Java 資源本地化與國際化

n672 9年前發布 | 25K 次閱讀 Java Java開發

資源包

 

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

 

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

 

ResourceBundle類

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

 

資源包中的每個成員共享一個被稱作基名(basename)的名稱,然后在此基礎上根據一定的命名規范進行擴展。下面就列出了一些成員的名稱:

    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對象作為參數傳入。

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

ResourceBundle.getBundle(message)//獲取當前系統所使用的區域環境獲得指定資源文件
ResourceBundle.getBundle(message,locale)//根據指定的區域獲取對應的資源文件

如果該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”))

       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

 

創建了具體的資源包子類實例以后,就需要獲得具體的信息。信息在資源包中是以鍵值對的方式存儲的

 

LabelResources.properties(實際上properties文件中不能直接保存中文,必須經過Unicode編碼)

# This is LabelResources.properties file
greetings = 您好!
farewell = 再見。
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(缺省類)

 

下面的代碼列出的是LabelBundle_zh_CN.java的源代碼,相對于資源文件中“key = value”的寫法,在此文件中你首先利用鍵值對來初始化一個二維數組,并在getContents方法中返回該數組。

     

LabelBundle_zh_CN.java

 

public class LabelBundle_zh_CN extends ListResourceBundle {  
  public Object[][] getContents() {    
    return contents;  
  }  
  private Object[][] contents = {     
    {"title", "稱謂"},     
    {"surname", "姓"},     
   {"firstname", "名"},  
  };
}

 

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

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

如果系統同時存在資源文件、類文件,系統將以類文件為主,而不會調用資源文件。

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屬性。

   

   MessageFormat formatter = newMessageFormat("");
   formatter.setLocale(currentLocale);

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

messages =     ResourceBundle.getBundle(  "MessagesBundle",currentLocale);
Object[]testArgs = {"peachpi",new Date()};<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>

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

     

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

關于資源包的組織

一般來說,你是按照資源的用途來組織資源包的,比如會把所有的頁面按鈕的信息放入一個名為ButtonResources的資源包中。在實際的應用過程中,以下幾個原則可以幫你決定如何組織資源包:

1、要易于維護。

2、最好不要將所有的信息都放入一個資源包中,因為這樣資源包載入內存時將會很耗時。

3、最好將一個大的資源包分為幾個小的資源包,這樣可以在使用的時候才導入必須的資源,減少內存消耗。

JSTL 國際化標簽

<fmt:bundle> 功能:指定消息資源使用的文件

<fmt:message>功能:顯示消息資源文件中指定key的消息,支持帶參數消息

<fmt:param> 功能:給帶參數的消息置參數值

<fmt:setBundle>功能:設置消息資源文件

一個支持按模塊的多資源文件的國際化例子

resources\IAMResources_zh_CN.properties,內容為

test.common.message= test.common.message {0}

resources\UserSynResources_zh_CN.properties內容為

test.usersyn.message= test.usersyn.message {0}

includeTld.jsp:

<fmt:setBundle basename="resources.IAMResources" var="commonBundle"/>
<fmt:setBundle basename="resources.UserSynResources" var="userSynBundle"/>

test.jsp

<%@includefile="/includeTld.jsp"%>
  
  <fmt:message key="test.common.message" bundle="${commonBundle}">
       <fmt:param value="clf"/>
  </fmt:message>
  
    <fmt:bunble basename=" ${commonBundle}">
       <fmt:messagekey="test.usersyn.message" >
              <fmt:paramvalue="clf"/>
       </fmt:message>
    </fmt:bundle>

 輸出:test.common.messageclf

         test.usersyn.message clf

 

 

 本文由用戶 n672 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!