本文是由JR主持寫(xiě)作的《J2SE進(jìn)階》一書(shū)的部分章節(jié)整理而成,《J2SE進(jìn)階》正在寫(xiě)作、完善階段。您閱讀后,有任何建議、批評(píng),請(qǐng)和我聯(lián)系,或在javaresearch.org/forum/thread.jsp?column=376&thread=7576′ ?> ;http://www.javaresearch.org/forum/thread.jsp?column=376&thread=7576′ target=′_blank′ class=′l2′>這兒留言。《J2SE進(jìn)階》寫(xiě)作項(xiàng)目組感謝您閱讀本文。
在當(dāng)今這個(gè)信息社會(huì),尤其是隨著互聯(lián)網(wǎng)的出現(xiàn)和普及,人們之間的距離比以往任何時(shí)候都更加接近,同時(shí)交往也更加頻繁,時(shí)下最時(shí)髦的概念就是地球村,而我小時(shí)候只知道我出生的那個(gè)小鄉(xiāng)村。距離近,交往頻繁,人們就不得不考慮如何去與各個(gè)不同種族、不同區(qū)域的人們打交道。對(duì)人如此,對(duì)我們的軟件亦是如此,你需要考慮如何讓處于世界不同地方的使用者都能夠很好地使用你的軟件。于是,在每個(gè)軟件開(kāi)始之前,編寫(xiě)者都可能需要考慮這樣一個(gè)問(wèn)題??國(guó)際化。 我們知道,在Java中可以通過(guò) java.util.Locale類(lèi)來(lái)唯一地確定特定語(yǔ)言和國(guó)家的組合,即抽象最終用戶(hù)的使用環(huán)境。同時(shí)將用戶(hù)相關(guān)的一些信息置于資源包中,通過(guò)資源包來(lái)動(dòng)態(tài)地獲得最終的用戶(hù)顯示。資源包可以由資源 文件或者資源子類(lèi)來(lái)具體實(shí)現(xiàn)。 注意:本文只打算討論國(guó)際化過(guò)程中資源包的使用技巧,更多更精彩的內(nèi)容,請(qǐng)期待《J2SE進(jìn)階》一書(shū)。 資源包在編寫(xiě)應(yīng)用程序的時(shí)候,需要面對(duì)的一個(gè)問(wèn)題是如何來(lái)處理與locale相關(guān)的一些信息。比如,頁(yè)面上的一些靜態(tài)文本就希望能夠以用戶(hù)習(xí)慣的語(yǔ)言顯示。最原始的做法是將這些信息硬編碼到程序中(可能是一大串判斷語(yǔ)句),但是這樣就將程序代碼和易變的locale信息捆綁在一起,以后如果需要修改locale信息或者添加其它的locale信息,你就不得不重新修改代碼。而資源包可以幫助你解決這個(gè)問(wèn)題,它通過(guò)將可變的locale信息放入資源包中來(lái)達(dá)到兩者分離的目的。應(yīng)用程序可以自動(dòng)地通過(guò)當(dāng)前的locale設(shè)置到相應(yīng)的資源包中取得所要的信息。資源包的概念類(lèi)似于Windows編程人員使用的資源文件(rc文件)。
一般來(lái)說(shuō),資源包需要完成兩個(gè)功能:和具體的locale進(jìn)行綁定以及讀取locale相關(guān)信息。
ResourceBundle類(lèi) 你可以把資源包看作為一個(gè)由許多成員(子類(lèi))組成的大家庭,其中每個(gè)成員關(guān)聯(lián)到不同的locale對(duì)象,那它是如何完成關(guān)聯(lián)功能的呢?
資源包中的每個(gè)成員共享一個(gè)被稱(chēng)作基名(base name)的名稱(chēng),然后在此基礎(chǔ)上根據(jù)一定的命名規(guī)范進(jìn)行擴(kuò)展。下面就列出了一些成員的名稱(chēng): LabelResources LabelResources_de LabelResources_de_CH LabelResources_de_CH_UNIX 可見(jiàn)這些子類(lèi)依據(jù)這樣的命名規(guī)范:baseName_language_country_variant,其中l(wèi)anguage等幾個(gè)變量就是你在構(gòu)造Locale類(lèi)時(shí)所使用的。而資源包正是通過(guò)這個(gè)符合命名規(guī)范的名稱(chēng)來(lái)和locale進(jìn)行關(guān)聯(lián)的,比如LabelResource_de_CH就對(duì)應(yīng)于由德語(yǔ)(de)和瑞士(CH)組成的locale對(duì)象。
當(dāng)你的應(yīng)用程序需要查找特定locale對(duì)象關(guān)聯(lián)的資源包時(shí),它可以調(diào)用ResourceBundle的getBundle方法,并將locale對(duì)象作為參數(shù)傳入。
Locale currentLocale = new Locale("de", "CH", "UNIX");
ResourceBundle myResources =
ResourceBundle.getBundle("LabelResources", currentLocale);
如果該locale對(duì)象匹配的資源包子類(lèi)找不到,getBundle將試著查找最匹配的一個(gè)子類(lèi)。具體的查找策略是這樣的:getBundle使用基名,locale對(duì)象和缺省的locale來(lái)生成一個(gè)候選資源包名稱(chēng)序列。如果特定locale對(duì)象的語(yǔ)言代碼、國(guó)家代碼和可選變量都是空值,則基名是唯一的候選資源包名稱(chēng)。否則的話(huà),具體locale對(duì)象(language1,country1和variant1)和缺省locale(language2,country2和variant2)將產(chǎn)生如下的序列:
baseName + "_" + language1 + "_" + country1 + "_" + variant1
baseName + "_" + language1 + "_" + country1
baseName + "_" + language1
baseName + "_" + language2 + "_" + country2 + "_" + variant2
baseName + "_" + language2 + "_" + country2
baseName + "_" + language2
baseName
然后,getBundle方法按照產(chǎn)生的序列依次查找匹配的資源包子類(lèi)并對(duì)結(jié)果子類(lèi)初始化。首先,它將尋找類(lèi)名匹配候選資源包名稱(chēng)的類(lèi),如果找到將創(chuàng)建該類(lèi)的一個(gè)實(shí)例,我們稱(chēng)之為結(jié)果資源包。否則,getBundle方法將尋找對(duì)應(yīng)的資源文件,它通過(guò)候選資源包名稱(chēng)來(lái)獲得資源文件的完整路徑(將其中的“.”替換為“/”,并加上“.properties”后綴),如果找到匹配文件,getBundle方法將利用該資源文件來(lái)創(chuàng)建一個(gè)PropertyResourceBundle實(shí)例,也就是最終的結(jié)果資源包。與此同時(shí),getBundle方法會(huì)將這些資源包實(shí)例緩存起來(lái)供以后使用。
如果沒(méi)有找到結(jié)果資源包,該方法將拋出MissingResourceException異常。所以為了防止異常的拋出,一般來(lái)說(shuō)都需要至少實(shí)現(xiàn)一個(gè)基名資源包子類(lèi)。
注意:基名參數(shù)必須是一個(gè)完整的類(lèi)名稱(chēng)(比如LabelResources,resource.LabelResources等),就相當(dāng)于你引用一個(gè)類(lèi)時(shí)需要指定完整的類(lèi)路徑。但是,為了和以前的版本保持兼容,在使用PropertyResourceBundles時(shí)也允許使用“/”來(lái)代替“.”表示路徑。
比如你有以下這些資源類(lèi)和資源文件:MyResources.class, MyResources_fr_CH.properties, MyResources_fr_CH.class, MyResources_fr.properties, MyResources_en.properties, MyResources_es_ES.class。你利用以下的locale設(shè)置來(lái)調(diào)用getBundle方法,你將會(huì)得到不同的結(jié)果資源包(假設(shè)缺省locale為L(zhǎng)ocale(“en”, “UK”)),請(qǐng)參考表13.4。 表13.4 locale設(shè)置與結(jié)果資源包 locale設(shè)置 結(jié)果資源包 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
創(chuàng)建了具體的資源包子類(lèi)實(shí)例以后,就需要獲得具體的信息。信息在資源包中是以鍵值對(duì)的方式存儲(chǔ)的,表13.5列出的是LabelResources.properties文件的內(nèi)容。
表13.5 LabelResources.properties
# This is LabelResources.properties file
greetings = 您好!
farewell = 再見(jiàn)。
inquiry = 您好嗎?
其中等號(hào)左邊的字符串表示主鍵,它們是唯一的。為了獲得主鍵對(duì)應(yīng)的值,你可以調(diào)用ResourceBundle類(lèi)的getString方法,并將主鍵作為參數(shù)。此外,文件中以“#”號(hào)開(kāi)頭的行表示注釋行。
ListResourceBundle和PropertyResourceBundle子類(lèi) 抽象類(lèi)ResourceBundle具有兩個(gè)子類(lèi):ListResourceBundle和PropertyResourceBundle,它們表示資源包子類(lèi)兩種不同的實(shí)現(xiàn)方式。
PropertyResourceBundle是和資源文件配對(duì)使用的,一個(gè)屬性文件就是一個(gè)普通的文本文件,你只需要為不同的locale設(shè)置編寫(xiě)不同名稱(chēng)的資源文件。但是,在資源文件中只能包含字符串,如果需要存儲(chǔ)其它類(lèi)型對(duì)象,你可以使用ListResourceBundle。
ListResourceBundle是將鍵值對(duì)信息保存在類(lèi)中的列表中,而且你必須實(shí)現(xiàn)ListResourceBundle的具體子類(lèi)。
如果ListResourceBundle和PropertyResourceBundle不能夠滿(mǎn)足你的需要,你可以實(shí)現(xiàn)自己的ResourceBundle子類(lèi),你的子類(lèi)必須覆蓋兩個(gè)方法:handleGetObject和getKeys。
使用資源包最簡(jiǎn)單的方法就是利用資源文件,利用資源文件一般需要以下幾個(gè)步驟: 1、創(chuàng)建一個(gè)缺省的資源文件 為了防止找不到資源文件,你最好實(shí)現(xiàn)一個(gè)缺省的資源文件,該文件的名稱(chēng)為資源包的基名加上.properties后綴。 2、創(chuàng)建所需的資源文件 為你準(zhǔn)備支持的locale設(shè)置編寫(xiě)對(duì)應(yīng)的資源文件。 3、設(shè)置locale 你必須在程序中的某個(gè)地方提供locale的設(shè)置或者切換功能,或者將其放入配置文件中。 4、根據(jù)locale設(shè)置創(chuàng)建資源包 ResourceBundle resource = ResourceBundle.getBundle("LabelBundle",currentLocale); 5、通過(guò)資源包獲取locale相關(guān)信息 String value = resource.getString("welcome");
注意:在使用基名的時(shí)候,特別要注意給出完整的類(lèi)名(或者路徑名),比如你的應(yīng)用程序所在的類(lèi)包為org.javaresearch.j2seimproved.i18n,而你的資源文件在你的應(yīng)用程序下的resource子目錄中,那你的基名就應(yīng)該是org.javaresearch.j2seimproved.i18n.resource.LabelBundleBundle而不是resource.LabelBundleBundle。
使用ListResourceBundle 使用ListResourceBundle和使用資源文件的步驟基本上一樣,只不過(guò)你需要用ListResourceBundle子類(lèi)來(lái)替換相應(yīng)的資源文件。比如你的應(yīng)用程序的基名是LabelBundle,而且準(zhǔn)備支持Locale("en","US")和Locale("zh","CN"),那你需要提供以下幾個(gè)Java文件,注意類(lèi)名和locale的對(duì)應(yīng)關(guān)系。 LabelBundle_en_US.java LabelBundle_zh_CN.java LabelBundle.java(缺省類(lèi))
代碼13.3列出的是LabelBundle_zh_CN.java的源代碼,相對(duì)于資源文件中“key = value”的寫(xiě)法,在此文件中你首先利用鍵值對(duì)來(lái)初始化一個(gè)二維數(shù)組,并在getContents方法中返回該數(shù)組。 代碼13.3:LabelBundle_zh_CN.java
package org.javaresearch.j2seimproved.i18n;import
java.util.java/util/ListResourceBundle.java.html" target="_blank">ListResourceBundle;
public class LabelBundle_zh_CN extends ListResourceBundle {
public java/lang/Object.java.html" target="_blank">Object[][] getContents() {
return contents;
}
private java/lang/Object.java.html" target="_blank">Object[][] contents = {
{"title", "稱(chēng)謂"},
{"surname", "姓"},
{"firstname", "名"},
};
}
創(chuàng)建完資源類(lèi)以后,同樣需要設(shè)置locale以及根據(jù)locale來(lái)創(chuàng)建資源包。在通過(guò)資源包獲取具體值的時(shí)候,你不能再使用getString方法,而應(yīng)該調(diào)用getObject方法,而且由于getObject方法返回一個(gè)Object對(duì)象,你還需要進(jìn)行正確的類(lèi)型轉(zhuǎn)換。其實(shí),為了你的程序通用性,我們建議在使用資源文件的時(shí)候你也應(yīng)該調(diào)用getObject方法,而不是getString方法。
java/lang/String.java.html" target="_blank">String title = (java/lang/String.java.html" target="_blank">String)resource.getObject("title");
關(guān)于ListResourceBundle的詳細(xì)使用,可以參考本書(shū)所附代碼中國(guó)際化一節(jié)的ListResourceBundleSample.java程序。
MessageFormat類(lèi) 上面我們講到利用資源文件來(lái)分離代碼和可變的信息。但是在實(shí)際過(guò)程中,有些信息并不能夠完全事先定義好,其中可能會(huì)用到運(yùn)行時(shí)的一些結(jié)果,最典型例子的就是錯(cuò)誤提示代碼,比如提示某個(gè)輸入必須在一定范圍內(nèi)。利用上面所講的資源文件并不能夠很好地解決這個(gè)問(wèn)題,所以Java中引入了MessageFormat類(lèi)。
MessageFormat提供一種語(yǔ)言無(wú)關(guān)的方式來(lái)組裝消息,它允許你在運(yùn)行時(shí)刻用指定的參數(shù)來(lái)替換掉消息字符串中的一部分。你可以為MessageFormat定義一個(gè)模式,在其中你可以用占位符來(lái)表示變化的部分,比如你有這樣一句話(huà):
您好,peachpi!歡迎來(lái)到Java研究組織網(wǎng)站!當(dāng)前時(shí)間是:2003-8-1 16:43:12。
其中斜體帶下劃線(xiàn)的部分為可變化的,你需要根據(jù)當(dāng)前時(shí)間和不同的登錄用戶(hù)來(lái)決定最終的顯示。我們用占位符來(lái)表示這些變化的部分,可以得到下面這個(gè)模式:
您好,{0}!歡迎來(lái)到Java研究組織網(wǎng)站!當(dāng)前時(shí)間是:{1,date} {1,time}。
占位符的格式為{ ArgumentIndex , FormatType , FormatStyle },詳細(xì)說(shuō)明可以參考MessageFormat的API說(shuō)明文檔。這里我們定義了兩個(gè)占位符,其中的數(shù)字對(duì)應(yīng)于傳入的參數(shù)數(shù)組中的索引,{0}占位符被第一個(gè)參數(shù)替換,{1}占位符被第二個(gè)參數(shù)替換,依此類(lèi)推。 最多可以設(shè)置10個(gè)占位符,而且每個(gè)占位符可以重復(fù)出現(xiàn)多次,而且格式可以不同,比如{1,date}和{1,time}。而通過(guò)將這些模式定義放到不同的資源文件中,就能夠根據(jù)不同的locale設(shè)置,得到不同的模式定義,并用參數(shù)動(dòng)態(tài)替換占位符。
下面我們就以MessageFormatSample.java程序(源文件見(jiàn)本書(shū)所附代碼)為例,來(lái)詳細(xì)說(shuō)明其中的每個(gè)步驟。 1、找出可變的部分,并據(jù)此定義模式,將模式放入不同的資源文件中。 比如針對(duì)上面的模式,定義了下面兩個(gè)資源文件: MessagesBundle_en_US.properties Welcome = Hi, {0}! Welcome to Java Research Organization! MessagesBundle_zh_CN.properties Welcome = 您好,{0}!歡迎來(lái)到Java研究組織網(wǎng)站!
2、創(chuàng)建MessageFormat對(duì)象,并設(shè)置其locale屬性。
MessageFormat formatter = new MessageFormat("");
formatter.setLocale(currentLocale);
3、從資源包中得到模式定義,以及設(shè)置參數(shù)。
messages = ResourceBundle.getBundle(
"org.javaresearch.j2seimproved.i18n.resource.MessagesBundle",currentLocale);
java/lang/Object.java.html" target="_blank">Object[] testArgs = {"peachpi",new Date()};
4、利用模式定義和參數(shù)進(jìn)行格式化。
java/lang/System.java.html" target="_blank">System.out.println(formatter.format(messages.getString("welcome"),testArgs));
關(guān)于資源包的組織 一般來(lái)說(shuō),你是按照資源的用途來(lái)組織資源包的,比如會(huì)把所有的頁(yè)面按鈕的信息放入一個(gè)名為ButtonResources的資源包中。在實(shí)際的應(yīng)用過(guò)程中,以下幾個(gè)原則可以幫你決定如何組織資源包: 1、要易于維護(hù)。 2、最好不要將所有的信息都放入一個(gè)資源包中,因?yàn)檫@樣資源包載入內(nèi)存時(shí)將會(huì)很耗時(shí)。 3、最好將一個(gè)大的資源包分為幾個(gè)小的資源包,這樣可以在使用的時(shí)候才導(dǎo)入必須的資源,減少內(nèi)存消耗。
|