Java國際化——資源套件的使用

來源:互聯網
上載者:User

 
本文是由JR主持寫作的《J2SE進階》一書的部分章節整理而成,《J2SE進階》正在寫作、完善階段。您閱讀後,有任何建議、批評,請和我聯絡,或在http://www.javaresearch.org/forum/thread.jsp?column=376&thread=7576' target='_blank' class='l2'>這兒留言。《J2SE進階》寫作項目組感謝您閱讀本文。

在當今這個資訊社會,尤其是隨著互連網的出現和普及,人們之間的距離比以往任何時候都更加接近,同時交往也更加頻繁,時下最時髦的概念就是地球村,而我小時候只知道我出生的那個小鄉村。距離近,交往頻繁,人們就不得不考慮如何去與各個不同種族、不同地區的人們打交道。對人如此,對我們的軟體亦是如此,你需要考慮如何讓處於世界不同地方的使用者都能夠很好地使用你的軟體。於是,在每個軟體開始之前,編寫者都可能需要考慮這樣一個問題——國際化。

我們知道,在Java中可以通過java.util.Locale類來唯一地確定特定語言和國家的組合,即抽象終端使用者的使用環境。同時將使用者相關的一些資訊置於資源套件中,通過資源套件來動態地獲得最終的使用者顯示。資源套件可以由資源檔或者資源子類來具體實現。

注意:本文只打算討論國際化過程中資源套件的提示,更多更精彩的內容,請期待《J2SE進階》一書。

資源套件

在編寫應用程式的時候,需要面對的一個問題是如何來處理與locale相關的一些資訊。比如,頁面上的一些靜態文本就希望能夠以使用者習慣的語言顯示。最原始的做法是將這些資訊寫入程式碼到程式中(可能是一大串判斷語句),但是這樣就將程式碼和易變的locale資訊捆綁在一起,以後如果需要修改locale資訊或者添加其它的locale資訊,你就不得不重新修改代碼。而資源套件可以協助你解決這個問題,它通過將可變的locale資訊放入資源套件中來達到兩者分離的目的。應用程式可以自動地通過當前的locale設定到相應的資源套件中取得所要的資訊。資源套件的概念類似於Windows編程人員使用的資源檔(rc檔案)。

一般來說,資源套件需要完成兩個功能:和具體的locale進行綁定以及讀取locale相關資訊。

ResourceBundle類

你可以把資源套件看作為一個由許多成員(子類)組成的大家庭,其中每個成員關聯到不同的locale對象,那它是如何完成關聯功能的呢?

資源套件中的每個成員共用一個被稱作基名(base name)的名稱,然後在此基礎上根據一定的命名規範進行擴充。下面就列出了一些成員的名稱:
    LabelResources
         LabelResources_de
         LabelResources_de_CH
         LabelResources_de_CH_UNIX
可見這些子類依據這樣的命名規範:baseName_language_country_variant,其中language等幾個變數就是你在構造Locale類時所使用的。而資源套件正是通過這個符合命名規範的名稱來和locale進行關聯的,比如LabelResource_de_CH就對應於由德語(de)和瑞士(CH)組成的locale對象。

當你的應用程式需要尋找特定locale對象關聯的資源套件時,它可以調用ResourceBundle的getBundle方法,並將locale對象作為參數傳入。

  1. Locale currentLocale = new Locale("de", "CH", "UNIX");
  2. ResourceBundle myResources =
  3.       ResourceBundle.getBundle("LabelResources", currentLocale);

如果該locale對象匹配的資源套件子類找不到,getBundle將試著尋找最匹配的一個子類。具體的尋找策略是這樣的:getBundle使用基名,locale對象和預設的locale來產生一個候選資源套件名稱序列。如果特定locale對象的語言代碼、國家代碼和可選變數都是空值,則基名是唯一的候選資源套件名稱。否則的話,具體locale對象(language1,country1和variant1)和預設locale(language2,country2和variant2)將產生如下的序列:

  • baseName + "_" + language1 + "_" + country1 + "_" + variant1
  • baseName + "_" + language1 + "_" + country1 
  • baseName + "_" + language1 
  • baseName + "_" + language2 + "_" + country2 + "_" + variant2 
  • baseName + "_" + language2 + "_" + country2 
  • baseName + "_" + language2 
  • baseName 

然後,getBundle方法按照產生的序列依次尋找匹配的資源套件子類並對結果子類初始化。首先,它將尋找類名匹配候選資源套件名稱的類,如果找到將建立該類的一個執行個體,我們稱之為結果資源套件。否則,getBundle方法將尋找對應的資源檔,它通過候選資源套件名稱來獲得資源檔的完整路徑(將其中的“.”替換為“/”,並加上“.properties”尾碼),如果找到匹配檔案,getBundle方法將利用該資源檔來建立一個PropertyResourceBundle執行個體,也就是最終的結果資源套件。與此同時,getBundle方法會將這些資源套件執行個體緩衝起來供以後使用。

如果沒有找到結果資源套件,該方法將拋出MissingResourceException異常。所以為了防止異常的拋出,一般來說都需要至少實現一個基名資源套件子類。

注意:基名參數必須是一個完整的類名稱(比如LabelResources,resource.LabelResources等),就相當於你引用一個類時需要指定完整的類路徑。但是,為了和以前的版本保持相容,在使用PropertyResourceBundles時也允許使用“/”來代替“.”表示路徑。

比如你有以下這些資源類和資源檔:MyResources.class, MyResources_fr_CH.properties, MyResources_fr_CH.class, MyResources_fr.properties, MyResources_en.properties, MyResources_es_ES.class。你利用以下的locale設定來調用getBundle方法,你將會得到不同的結果資源套件(假設預設locale為Locale(“en”, “UK”)),請參考表13.4。
       表13.4 locale設定與結果資源套件
locale設定        結果資源套件
Locale("fr", "CH")    MyResources_fr_CH.class
Locale("fr", "FR")        MyResources_fr.properties
Locale("de", "DE")        MyResources_en.properties
Locale("en", "US")        MyResources_en.properties
Locale("es", "ES")        MyResources_es_ES.class

建立了具體的資源套件子類執行個體以後,就需要獲得具體的資訊。資訊在資源套件中是以索引值對的方式儲存的,表13.5列出的是LabelResources.properties檔案的內容。

表13.5 LabelResources.properties

  1. # This is LabelResources.properties file
  2. greetings = 您好!
  3. farewell = 再見。
  4. inquiry = 您好嗎?

其中等號左邊的字串表示主鍵,它們是唯一的。為了獲得主鍵對應的值,你可以調用ResourceBundle類的getString方法,並將主鍵作為參數。此外,檔案中以“#”號開頭的行表示注釋行。

ListResourceBundle和PropertyResourceBundle子類

抽象類別ResourceBundle具有兩個子類:ListResourceBundle和PropertyResourceBundle,它們表示資源套件子類兩種不同的實現方式。

PropertyResourceBundle是和資源檔配對使用的,一個屬性檔案就是一個普通的文字檔,你只需要為不同的locale設定編寫不同名稱的資源檔。但是,在資源檔中只能包含字串,如果需要儲存其它類型對象,你可以使用ListResourceBundle。

ListResourceBundle是將索引值對資訊儲存在類中的列表中,而且你必須實現ListResourceBundle的具體子類。

如果ListResourceBundle和PropertyResourceBundle不能夠滿足你的需要,你可以實現自己的ResourceBundle子類,你的子類必須覆蓋兩個方法:handleGetObject和getKeys。

使用資源檔

使用資源套件最簡單的方法就是利用資源檔,利用資源檔一般需要以下幾個步驟:
1、建立一個預設的資源檔
為了防止找不到資源檔,你最好實現一個預設的資源檔,該檔案的名稱為資源套件的基名加上.properties尾碼。
2、建立所需的資源檔
為你準備支援的locale設定編寫對應的資源檔。
3、設定locale
你必須在程式中的某個地方提供locale的設定或者切換功能,或者將其放入設定檔中。
4、根據locale設定建立資源套件
ResourceBundle resource =
        ResourceBundle.getBundle("LabelBundle",currentLocale);
5、通過資源套件擷取locale相關資訊
String value = resource.getString("welcome");

注意:在使用基名的時候,特別要注意給出完整的類名(或者路徑名),比如你的應用程式所在的類包為org.javaresearch.j2seimproved.i18n,而你的資源檔在你的應用程式下的resource子目錄中,那你的基名就應該是org.javaresearch.j2seimproved.i18n.resource.LabelBundleBundle而不是resource.LabelBundleBundle。

使用ListResourceBundle

使用ListResourceBundle和使用資源檔的步驟基本上一樣,只不過你需要用ListResourceBundle子類來替換相應的資源檔。比如你的應用程式的基名是LabelBundle,而且準備支援Locale("en","US")和Locale("zh","CN"),那你需要提供以下幾個Java檔案,注意類名和locale的對應關係。
LabelBundle_en_US.java
LabelBundle_zh_CN.java
LabelBundle.java(預設類)

代碼13.3列出的是LabelBundle_zh_CN.java的原始碼,相對於資源檔中“key = value”的寫法,在此檔案中你首先利用索引值對來初始化一個二維數組,並在getContents方法中返回該數組。
      
代碼13.3:LabelBundle_zh_CN.java

  1. package org.javaresearch.j2seimproved.i18n;import 
  2. java.util.ListResourceBundle;
  3. public class LabelBundle_zh_CN extends ListResourceBundle {   
  4.   public Object[][] getContents() {     
  5.     return contents;   
  6.   }   
  7.   private Object[][] contents = {      
  8.     {"title", "稱謂"},      
  9.     {"surname", "姓"},      
  10.     {"firstname", "名"},   
  11.   };
  12. }

建立完資源類以後,同樣需要設定locale以及根據locale來建立資源套件。在通過資源套件擷取具體值的時候,你不能再使用getString方法,而應該調用getObject方法,而且由於getObject方法返回一個Object對象,你還需要進行正確的類型轉換。其實,為了你的程式通用性,我們建議在使用資源檔的時候你也應該調用getObject方法,而不是getString方法。

  1.     String title = (String)resource.getObject("title");

關於ListResourceBundle的詳細使用,可以參考本書所附代碼中國際化一節的ListResourceBundleSample.java程式。

MessageFormat類

上面我們講到利用資源檔來分離代碼和可變的資訊。但是在實際過程中,有些資訊並不能夠完全事先定義好,其中可能會用到運行時的一些結果,最典型例子的就是錯誤提示代碼,比如提示某個輸入必須在一定範圍內。利用上面所講的資源檔並不能夠很好地解決這個問題,所以Java中引入了MessageFormat類。

MessageFormat提供一種語言無關的方式來組裝訊息,它允許你在運行時刻用指定的參數來替換掉訊息字串中的一部分。你可以為MessageFormat定義一個模式,在其中你可以用預留位置來表示變化的部分,比如你有這樣一句話:

您好,peachpi!歡迎來到Java研究組織網站!目前時間是:2003-8-1 16:43:12。

其中斜體帶底線的部分為可變化的,你需要根據目前時間和不同的登入使用者來決定最終的顯示。我們用預留位置來表示這些變化的部分,可以得到下面這個模式:

您好,{0}!歡迎來到Java研究組織網站!目前時間是:{1,date} {1,time}。

預留位置的格式為{ ArgumentIndex , FormatType , FormatStyle },詳細說明可以參考MessageFormat的API說明文檔。這裡我們定義了兩個預留位置,其中的數字對應於傳入的參數數組中的索引,{0}預留位置被第一個參數替換,{1}預留位置被第二個參數替換,依此類推。
最多可以設定10個預留位置,而且每個預留位置可以重複出現多次,而且格式可以不同,比如{1,date}和{1,time}。而通過將這些模式定義放到不同的資源檔中,就能夠根據不同的locale設定,得到不同的模式定義,並用參數動態替換預留位置。

下面我們就以MessageFormatSample.java程式(源檔案見本書所附代碼)為例,來詳細說明其中的每個步驟。
1、找出可變的部分,並據此定義模式,將模式放入不同的資源檔中。
比如針對上面的模式,定義了下面兩個資源檔:
MessagesBundle_en_US.properties
Welcome = Hi, {0}! Welcome to Java Research Organization!
MessagesBundle_zh_CN.properties
Welcome = 您好,{0}!歡迎來到Java研究組織網站!

2、建立MessageFormat對象,並設定其locale屬性。

  1.       MessageFormat formatter = new MessageFormat("");
  2.       formatter.setLocale(currentLocale);

3、從資源套件中得到模式定義,以及設定參數。

  1. messages =     ResourceBundle.getBundle(
  2.   "org.javaresearch.j2seimproved.i18n.resource.MessagesBundle",currentLocale);
  3. Object[] testArgs = {"peachpi",new Date()};

4、利用模式定義和參數進行格式化。

  1.       System.out.println(formatter.format(messages.getString("welcome"),testArgs));

關於資源套件的組織

一般來說,你是按照資源的用途來組織資源套件的,比如會把所有的頁面按鈕的資訊放入一個名為ButtonResources的資源套件中。在實際的應用過程中,以下幾個原則可以幫你決定如何組織資源套件:
1、要易於維護。
2、最好不要將所有的資訊都放入一個資源套件中,因為這樣資源套件載入記憶體時將會很耗時。
3、最好將一個大的資源套件分為幾個小的資源套件,這樣可以在使用的時候才匯入必須的資源,減少記憶體消耗。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.