國際化英文單詞為:Internationalization,又稱I18N,I為因為單詞的第一個字母,18為這個單詞的長度,而N代表這個單詞的最後一個字母。國際化又稱本地化(Localization,L10N)。 Java國際化主要通過如下3個類完成 java.util.ResourceBundle:... ...
國際化英文單詞為:Internationalization,又稱I18N,I為因為單詞的第一個字母,18為這個單詞的長度,而N代表這個單詞的最後一個字母。國際化又稱本地化(Localization,L10N)。
Java國際化主要通過如下3個類完成
- java.util.ResourceBundle:用於載入一個資源包
- java.util.Locale:對應一個特定的國家/區域、語言環境。
- java.text.MessageFormat:用於將消息格式化
為實現程式的國際化,必須提供程式所需要的資源文件。資源文件的內容由key-value對組成,資源文件的命名可以有3種格式:
- basename_language_country.properties
- basename_language.properties
- basename_properties
若資源文件包含非西方字元,則需要用JDK自帶的工具來處理:native2ascii,這個工具的語法格式如下:
native2ascii 資源文件名 目標資源文件名
如:
native2ascii mess_zh_XXX.proerties mess_zh_CN.proerties
Locale類可獲取各國區域環境(如:Locale.ENGLISH、Locale.CHINESE,這些常量返回一個Locale實例),也可以獲取當前系統所使用的區域語言環境,也可以使用語言和區域來創建Locale對象,Java的本地語言使用的時國際化標準組織(ISO)所定義的編碼,本地語言由小寫的兩個字母的代碼表示,遵循ISO-639-1;國家代碼由大寫的兩個字母的代碼組成,遵循ISO-3166-1,下表為常用的代碼:
語言 | 代碼 | 國家 | 代碼 |
Chinese | zh | China | CN |
English | en | United States | US |
Japanese | ja | Japan | JP |
可以使用Locale的靜態方法 getAvailableLocales 來獲取所支持的語言和國家,該方法返回一個Locale數組,該數組裡包含了java所支持的語言和國家,代碼如下:
Locale[] availableLocales = Locale.getAvailableLocales();
for (Locale l : availableLocales) {
System.out.println("Locale DisplayName=" + l.getDisplayName() + " Country=" + l.getCountry() + " Language="
+ l.getLanguage());
}
- 數字格式
數字和貨幣的格式時高度依賴與Locale的,Java類庫提供了一個格式器(formatter)對象的集合,可以對java.text包中的數字值進行格式化和解析,可以通過下麵的步驟來對特定的Locale的數字進行格式化:
- 獲取Locale對象
- 使用工廠方法獲取格式器對象,工廠方法時 NumberFormat 類的靜態方法,接受一個Locale 類型的參數,有三個工廠方法:getNumberInstance(數字)、getCurrencyInstance(貨幣) 和 getPercentInstance(百分比)
- 使用這個格式器對象來完成格式化和解析工作
格式化示例代碼:
Locale deLocale = new Locale("de", "DE");
NumberFormat currFmt = NumberFormat.getCurrencyInstance(deLocale);
double amt = 123878.34;
String formatResult = currFmt.format(amt);
System.out.println("amt=" + amt + " Format=" + formatResult);
如果想要讀取一個按照某個Locale的慣用法而輸入或存儲的數字,那邊就需要使用 parse 方法。
解析示例代碼:
Localede Locale=newLocale("de","DE");
NumberFormat currFmt=NumberFormat.getCurrencyInstance(deLocale);
Number input=currFmt.parse(formatResult);
double parseAmt=input.doubleValue();
System.out.println("FormatAmt="+formatResult+"ParseAmt="+parseAmt);
- 日期和時間
當格式化日期和時間時,需要考慮4個與Locale相關的問題,例如:月份和星期應該用本地語言來表示;年月日的順序要符合本地習慣;西曆可能不是本地首選的日期表示方法;必須要考慮本地時區。Java 使用 DateFormat 類來處理這些問題,和 NumberFormat 類很類似,調用 DateFormat 類的靜態方法,並傳入 Locale 來實例化,還需要設置日期或時間的格式化值,DateFormat 有如下三個工廠方法:
DateFormat.getDateInstance(dateStyle,loc);
DateFormat.getTimeInstance(timeStyle,loc);
DateFormat.getDateTimeInstance(dateStyle,timeStyle,loc);
其日期和時間的風格使用 DateFormat 的靜態常量來表示,常用的靜態常量如下:
DateFormat.DEFAULT
DateFormat.FULL
DateFormat.LONG
DateFormat.MEDIUM
DateFormat.SHORT
示例代碼如下:
String fmt = "";
Date nowDate = new Date();
DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, zhLocale);
fmt = dateFormat.format(nowDate);
System.out.println("Short style date string " + fmt);
dateFormat = DateFormat.getTimeInstance(DateFormat.MEDIUM, zhLocale);
fmt = dateFormat.format(nowDate);
System.out.println("MEDIUM style time string " + fmt);
dateFormat = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL, zhLocale);
fmt = dateFormat.format(nowDate);
System.out.println("FULL style date FULL style time string " + fmt);
如果要解析一個用戶輸入的日期,可以使用 DateFormat 類的 parse 方法,但其輸入的格式必須和創建 DateFormat 對象設置的風格一致,如果不一致則會拋出 IllegalArgumentException 異常,預設支持寬鬆的轉換,比如日期 2017年2月30日,會被解析為 2017年3月2日,如果希望關閉寬鬆的轉換,需要設置 lenient 標識,示例代碼如下:
dateFormat.setLenient(false);
Date convertDate = dateFormat.parse(fmt);
System.out.println("parse string " + fmt + " convertDate " + convertDate);
- 字元串排序
Java 中的排序時用Unicode字元來決定順序的,比如小寫字母的Unicode值比大寫的大,有重音符的字母的值甚至更大,這樣將使結果失去意義,如果需要定義排序的強度,可以使用Locale對象來創建 Collator 類,該類繼承了 Comparator介面,因此可以將該對象傳遞給 Collections.soft 方法來進行排序,示例代碼如下:
List<String> list = new LinkedList<>();
list.add("America");
list.add("Zulu");
list.add("able");
list.add("zebra");
Collator collator = Collator.getInstance(zhLocale);
// 設置排序強度
collator.setStrength(Collator.PRIMARY);
Collections.sort(list, collator);
可以設置排序強度來選擇不同的排序行為,字元間的差別可以被分為首要的(primary)、其次的(secndary)和再次的(tertiary)。比如,在英語中,A 和 Z 之間的差異被歸類為首要的;A 和 Å (重音符)之間的差異是其次的;A 和 a 之間是再次的,可以使用方法 setStrength 來設置排序強度,Collator.PRIMARY 表示首要的、Collator.SECONDARY 表示其次的、 Collator.TERTIARY 表示再次的,而Collator.IDENTICAL 則表示不允許有任何差異。
偶爾我們會碰到一個字元或字元序列在描述成Unicode時,可以有多種方式,例如,字母序列"ffi"可以用代碼U+FB03描述成單個字元"拉丁小連字ffi",Unicode 標準對字元串定義了四種範式形式:D、KD、C和KC,其中 D 和 KD 時用於排序的,在範化形式 D 重,重音字元被分解為基字元和組合重音符;範化形式 KD 更進一步將相容性字元也進行了分解,例如 ffi 連字元或商標符號TM,我們可以選擇排序器所使用的範化程度:Collator.NO_DECOMPOSITION表示不對字元串做任何範化;Collator.CANONICAL_DECOMPOSITION 使用範化形式 D;Collator.FULL_DECOMPOSITION 使用範化形式 KD。可以使用方法 setDecomposition 來設置分解模式
- 消息格式化
Java 類庫中有一個 MessageFormat 類,用來格式化帶變數的文本,就像這樣:"今天 {0} 是個好日子,{1} 公司給我發了工資 {2} 元",括弧中的數字是一個占位符,可以用實際的名字和值來替換他,使用靜態的 MessageFormat.format 方法,該方法使用當前系統的 Locale 對值進行格式化,如果需要用指定的 Locale 來進行格式化,需要使用 MessageFormat 實例的 format 方法,示例代碼如下:
String mfString = "今天 {0} 是個好日子,{1} 公司給我發了工資 {2} 元";
String formatString = MessageFormat.format(mfString, new Date(), "匯元1", 50000);
System.out.println(formatString);
MessageFormat mf = new MessageFormat(mfString, zhLocale);
formatString = mf.format(new Object[]{new Date(), "匯元2", 50000});
System.out.println(formatString);
如果還想在指定占位符的同時設置類型和樣式,可以按照如下格式:
String mfString = "今天 {0,date,long} 是個好日子,{1} 公司給我發了工資 {2,number,currency} 元";
mf = new MessageFormat(mfString, zhLocale);
formatString = mf.format(new Object[]{new Date(), "匯元3", 50000});
System.out.println(formatString);
輸出內容
今天 2017年5月26日 是個好日子,匯元3 公司給我發了工資 ¥50,000.00 元
占位符索引後面可以跟一個類型和樣式,之間用逗號隔開,類型可以是number、time、date、choice,如果類型是 number 則樣式有 integer、currency、percent;如果類型是 time 或 date,那麼樣式有 short、medium、long、full 或者是一個日期模式(yyyy-MM-dd);choice 表示希望消息跟隨占位符的值而變化,選項格式是由一個序列對構成的,每個序列對包含一個下限和一個格式化字元串,下限和字元串使用#號分隔,對於對之間用 | 分隔,示例如下:
mfString = "今天 {0,date,yyyy-MM-dd} 是個好日子,{1,choice,0#匯元|1#匯元1|2#匯元2} 公司給我發了工資 {2,number,currency} 元";
mf.applyPattern(mfString);
formatString = mf.format(new Object[]{new Date(), 1, 50000});
System.out.println(formatString);
可以使用 < 符號或 ≤ 符號 來替換 # ,則表示值小於或者小於等於下限值,示例代碼如下:
// choice說明: 這個表示 小於 1000 並且 1001-5000 的使用"可憐" ,5001-50000 的使用"不夠",50001 以上為"正好"
mfString = "今天 {0,date,yyyy-MM-dd} 是個好日子,{1} 公司給我發了工資 {2,number,currency} 元,{2,choice,1000<可憐|5000<不夠|50000<正好}";
mf.applyPattern(mfString);
formatString = mf.format(new Object[]{new Date(), "匯元4", 60000});
System.out.println(formatString
輸入內容
今天 2017-05-26 是個好日子,匯元4 公司給我發了工資 ¥60,000.00 元,正好
- 資源包
當本地化一個應用時,可能會有大量的消息字元串、按鈕標簽和其他的東西需要被翻譯,為了能靈活的完成這項任務,你會希望外部定義消息字元串,通常稱之為資源(resource),翻譯人員不需要接觸程式源代碼就可以很容易的編輯資源文件,在Java 中使用屬性文件來設定字元串資源,並未其他類型的資源實現相應的類。
當本地化一個應用時,會製造出很多資源包(resource bundle),每一個包都是一個屬性文件或者一個描述了玉locale相關的項的類,對於每一個包,都要為所有你想要支持的locale提供相應的版本,並需要對這些包使用一種統一的命名規則,例如,為中國定義的資源放在一個名為"包名_zh_CN"的文件中,而為所有使用中文簡體的國家所共用的資源則放在名為"包名_zh"的文件中,一般來說,使用
包名_語言_國家
來命名所有和國家相關的資源,使用
包名_語言
來命名所有和語言相關的資源,最後,作為後備,可以把預設資源放到一個沒有尾碼的文件中,可以使用下麵的代碼載入一個包:
ResourceBundle resourceBundle = ResourceBundle.getBundle(bundleName, locale);
getBundle 方法載入包的順序如下:
- 包名_當前locale的語言_當前locale的國家_當前locale的變數
- 包名_當前locale的語言_當前locale的國家
- 包名_當前locale的語言
- 包名_預設locale的語言_預設locale的國家_預設locale的變數
- 包名_預設locale的語言_預設locale的國家
- 包名_預設locale的語言
- 包名
- 拋出 MissingResourceException 異常
一旦getBundle 方法定位了一個包,比如,"包名_zh_CN" ,他還會繼續查找"包名_zh"和"包名"這二個包,如果這些包也存在,他們在資源層次中就稱為了"包名_zh_CN"的父包,以後查找資源的時候,如果在當前包中沒有找到,就會去查找其父包。
- 屬性文件
屬性文件是為了提供字元串資源常用的文件,每行存放一個鍵-值對的文本文件,比如,MyProgramStrings.properties 就是一個屬性文件,存儲屬性文件都是ASCII文件,然後可以使用 native2ascii 工具來產生MyProgramStrings_語言_國家.properties 文件,示例如下:
native2ascii MyProgramStrings.properties MyProgramStrings_zh_CN.properties
要查找一個具體的字元串,可以調用:
String resourceText= resourceBundle.getString("show.text");
示例資源文件內容:
show.text=\u8fd9\u4e2a\u662f\u8d44\u6e90\u6587\u4ef6\u7684\u5185\u5bb9
- 包類
為了提供字元串以外的資源,需要定義類,必須繼承 ResourceBundle 類(簡單的方法是繼承 ListResourceBundle類),應該使用標準的命名規則來命名類,比如:
MyProgramResource.java
MyProgramResource_zh.java
MyProgramResource_zh_CN.java
可以使用與載入屬性文件相同的 getBundle 方法來載入這個類,其 bundleName 為 資源包類的完整命名(包名和類名):
ResourceBundle resourceBundle1Class =
ResourceBundle.getBundle("locale.MyProgramResource", Locale.SIMPLIFIED_CHINESE);
要查找一個字元串或其他類型資源可以調用:
resourceText = resourceBundle1Class.getString("resourceClass.show.text");
Object resourceObj = resourceBundle1Class.getObject("resourceClass.show.obj");
如果一個Key同時存在屬性文件和包類,則包類的優先。