0 前言
全是幹貨的技術殿堂
文章收錄在我的 GitHub 倉庫,歡迎Star/fork:
Java-Interview-Tutorial https://github.com/Wasabi1234/Java-Interview-Tutorial
開發一個支援多國語言的Web應用程式,要求系統能夠根據用戶端的系統的語言類型傳回對應的界面:英文的作業系統傳回英文界面,而中文的作業系統則傳回中文界面——這便是典型的i18n國際化問題。
對于有國際化要求的應用系統,我們不能簡單地采用寫死的方式編寫使用者界面資訊、報錯資訊等内容,而必須為這些需要國際化的資訊進行特殊處理。簡單來說,就是為每種語言提供一套相應的資源檔案,并以規範化命名的方式儲存在特定的目錄中,由系統自動根據用戶端語言選擇适合的資源檔案。
1 簡介
“國際化資訊”也稱為“本地化資訊”,一般需要兩個條件才可以确定一個特定類型的本地化資訊
- “語言類型”
- “國家/地區的類型”
如中文本地化資訊既有中國大陸地區的中文,又有中國台灣、中國香港地區的中文,還有新加坡地區的中文。Java通過java.util.Locale類表示一個本地化對象,它允許通過語言參數和國家/地區參數建立一個确定的本地化對象。
語言參數使用ISO标準語言代碼表示,這些代碼是由ISO-639标準定義的,每一種語言由兩個小寫字母表示。在許多網站上都可以找到這些代碼的完整清單
國家/地區參數也由标準的ISO國家/地區代碼表示,這些代碼是由ISO-3166标準定義的,每個國家/地區由兩個大寫字母表示
- 檢視ISO-3166的标準代碼 http://www.iso.ch/iso/en/prods-services/iso3166ma/02iso-3166-code-lists/list-en1.html
- 一些語言和國家/地區的标準代碼
2 Locale 類
java.util.Locale是表示語言和國家/地區資訊的本地化類,是建立國際化應用的基礎。下面給出幾個建立本地化對象的示例:
//①帶有語言和國家/地區資訊的本地化對象
Locale locale1 = new Locale("zh","CN");
//②隻有語言資訊的本地化對象
Locale locale2 = new Locale("zh");
//③等同于Locale("zh","CN")
Locale locale3 = Locale.CHINA;
//④等同于Locale("zh")
Locale locale4 = Locale.CHINESE;
//⑤擷取本地系統預設的本地化對象
Locale locale 5= Locale.getDefault();
使用者既可以同時指定語言和國家/地區參數定義一個本地化對象①
也可以僅通過語言參數定義一個泛本地化對象②
Locale類中通過靜态常量定義了一些常用的本地化對象,③和④處就直接通過引用常量傳回本地化對象
使用者還可以擷取系統預設的本地化對象,如⑤
在測試時,如果希望改變系統預設的本地化設定,可以在啟動JVM時通過指令參數指定:
java -Duser.language=en -Duser.region=US MyTest。
本地化工具類
JDK的java.util包中提供了幾個支援本地化的格式化操作工具類:NumberFormat、DateFormat、MessageFormat。下面,我們分别通過執行個體了解它們的用法:
Locale locale = new Locale("zh", "CN");
NumberFormat currFmt = NumberFormat.getCurrencyInstance(locale);
double amt = 123456.78;
System.out.println(currFmt.format(amt));
上面的執行個體通過NumberFormat按本地化的方式對貨币金額進行格式化操作,運作執行個體,輸出以下資訊:
Locale locale = new Locale("en", "US");
Date date = new Date();
DateFormat df = DateFormat.getDateInstance(DateFormat.MEDIUM, locale);
System.out.println(df.format(date));
通過DateFormat#getDateInstance(int style,Locale locale)方法按本地化的方式對日期進行格式化操作。該方法第一個入參為時間樣式,第二個入參為本地化對象。運作以上代碼,輸出以下資訊:
Jan 8, 2007
MessageFormat在NumberFormat和DateFormat的基礎上提供了強大的占位符字元串的格式化功能,它支援時間、貨币、數字以及對象屬性的格式化操作。下面的執行個體示範了一些常見的格式化功能:
//①資訊格式化串
String pattern1 = "{0},你好!你于{1}在工商銀行存入{2} 元。";
String pattern2 = "At {1,time,short} On{1,date,long},{0} paid {2,number, currency}.";
//②用于動态替換占位符的參數
Object[] params = {"John", new GregorianCalendar().getTime(),1.0E3};
//③使用預設本地化對象格式化資訊
String msg1 = MessageFormat.format(pattern1,params);
//④使用指定的本地化對象格式化資訊
MessageFormat mf = new MessageFormat(pattern2,Locale.US);
String msg2 = mf.format(params);
System.out.println(msg1);
System.out.println(msg2);
pattern1是簡單形式的格式化資訊串,通過{n}占位符指定動态參數的替換位置索引,{0}表示第一個參數,{1}表示第二個參數,以此類推。
pattern2格式化資訊串比較複雜一些,除參數位置索引外,還指定了參數的類型和樣式。從pattern2中可以看出格式化資訊串的文法是很靈活的,一個參數甚至可以出現在兩個地方:如 {1,time,short}表示從第二個入參中擷取時間部分的值,顯示為短樣式時間;而{1,date,long}表示從第二個入參中擷取日期部分的值,顯示為長樣式時間。關于MessageFormat更詳細的使用方法,請參見JDK的Javadoc。
在②處,定義了用于替換格式化占位符的動态參數,這裡,我們使用到了JDK5.0自動裝包的文法,否則必須采用封裝類表示基本類型的參數值。
在③處,通過MessageFormat的format()方法格式化資訊串。它使用了系統預設的本地化對象,由于我們是中文平台,是以預設為Locale.CHINA。而在④處,我們顯式指定MessageFormat的本地化對象。
運作上面的代碼,輸出以下資訊:
John,你好!你于07-1-8 下午9:58在工商銀行存入1,000元。
At 9:58 PM OnJanuary 8, 2007,John paid $1,000.00.
資源檔案/屬性檔案
應用系統中某些資訊需要支援國際化功能,則必須為希望支援的不同本地化類型分别提供對應的資源檔案,并以規範的方式進行命名。國際化資源檔案的命名規範規定資源名稱采用以下的方式進行命名:
<資源名>_<語言代碼>_<國家/地區代碼>.properties
其中,語言代碼和國家/地區代碼都是可選的。<資源名>.properties命名的國際化資源檔案是預設的資源檔案,即某個本地化類型在系統中找不到對應的資源檔案,就采用這個預設的資源檔案。<資源名>_<語言代碼>.properties命名的國際化資源檔案是某一語言預設的資源檔案,即某個本地化類型在系統中找不到精确比對的資源檔案,将采用相應語言預設的資源檔案。
舉一個例子:假設資源名為resource,則語言為英文,國家為美國,則與其對應的本地化資源檔案命名為resource_en_US.properties。資訊在資源檔案以屬性名/值的方式表示:
引用
greeting.common=How are you!
greeting.morning = Good morning!
greeting.afternoon = Good Afternoon!
對應語言為中文,國家/地區為中國大陸的本地化資源檔案則命名為resource_zh_ CN.properties,資源檔案内容如下:
greeting.common=\u60a8\u597d\uff01
greeting.morning=\u65e9\u4e0a\u597d\uff01
greeting.afternoon=\u4e0b\u5348\u597d\uff01
本地化不同的同一資源檔案,雖然屬性值各不相同,但屬性名卻是相同的,這樣應用程式就可以通過Locale對象和屬性名精确調用到某個具體的屬性值了。
上面中文的本地化資源檔案内容采用了特殊的編碼表示中文字元,這是因為資源檔案對檔案内容有嚴格的要求:隻能包含ASCII字元。是以必須将非ASCII字元的内容轉換為Unicode代碼的表示方式。如上面中文的resource_zh_CN.properties資源檔案的三個屬性值分别是“您好!”、“早上好!”和“下午好!”三個中文字元串對應的Unicode代碼串。
如果在應用開發時,直接采用Unicode代碼編輯資源檔案是很不友善的,是以,通常我們直接使用正常的方式編寫資源檔案,在測試或部署時再采用工具進行轉換。JDK在bin目錄下為我們提供了一個完成此項功能的native2ascii工具,它可以将中文字元的資源檔案轉換為Unicode代碼格式的檔案,指令格式如下:
native2ascii [-reverse] [-encoding 編碼] [輸入檔案 [輸出檔案]]
resource_zh_CN.properties包含中文字元并且以UTF-8進行編碼,假設将該資源檔案放到d:\目錄下,通過下面的指令就可以将其轉換為Unicode代碼的形式:
D:\>native2ascii -encoding utf-8 d:\resource_zh_CN.properties
d:\resource_zh_CN_1.properties
由于原資源檔案采用UTF-8編碼,是以必須顯式通過-encoding指定編碼格式。
通過native2ascii指令手工轉換資源檔案,不但在操作上不友善,轉換後資源檔案中的屬性内容由于采用了ASCII編碼,閱讀起來也不友善。很多IDE開發工具都有屬性編輯器的插件,插件會自動将資源檔案内容轉換為ASCII形式的編碼,同時以正常的方式閱讀和編輯資源檔案的内容,這給開發和維護帶來了很大的便利。對于MyEclipse來說,使用MyEclipse Properties Editor編輯資源屬性檔案;對于Intellij IDEA來說,無須安裝任何插件就自然支援資源屬性檔案的這種編輯方式了。
如果應用程式中擁有大量的本地化資源檔案,直接通過傳統的File操作資源檔案顯然太過笨拙。Java為我們提供了用于加載本地化資源檔案的友善類java.util.ResourceBoundle。
ResourceBoundle為加載及通路資源檔案提供便捷的操作,下面的語句從相對于類路徑的目錄中加載一個名為resource的本地化資源檔案:
ResourceBundle rb = ResourceBundle.getBundle("com/baobaotao/i18n/resource", locale)
通過以下的代碼即可通路資源檔案的屬性值:
rb.getString("greeting.common")
來看下面的執行個體:
ResourceBundle rb1 = ResourceBundle.getBundle("com/baobaotao/i18n/resource", Locale.US);
ResourceBundle rb2 = ResourceBundle.getBundle("com/baobaotao/i18n/resource", Locale.CHINA);
System.out.println("us:"+rb1.getString("greeting.common"));
System.out.println("cn:"+rb2.getString("greeting.common"));
rb1加載了對應美國英語本地化的resource_en_US.properties資源檔案;而rb2加載了對應中國大陸中文的resource_zh_CN.properties資源檔案。運作上面的代碼,将輸出以下資訊:
us:How are you!
cn:您好!
加載資源檔案時,如果不指定本地化對象,将使用本地系統預設的本地化對象。是以,在中文系統中,ResourceBundle.getBundle(“com/baobaotao/i18n/resource”)語句也将傳回和代碼清單5-14中rb2相同的本地化資源。
ResourceBundle在加載資源時,如果指定的本地化資源檔案不存在,它按以下順序嘗試加載其他的資源:本地系統預設本地化對象對應的資源→預設的資源。上面的例子中,假設我們使用ResourceBundle.getBundle(“com/baobaotao/i18n/resource”,Locale.CANADA)加載資源,由于不存在resource_en_CA.properties資源檔案,它将嘗試加載resource_zh_CN.properties的資源檔案,假設resource_zh_CN.properties資源檔案也不存在,它将繼續嘗試加載resource.properties的資源檔案,如果這些資源都不存在,将抛出java.util.MissingResourceException異常。
在資源檔案中使用格式化串
在上面的資源檔案中,屬性值都是一般的字元串,它們不能結合運作時的動态參數構造出靈活的資訊,而這種需求是很常見的。要解決這個問題很簡單,隻須使用帶占位符的格式化串作為資源檔案的屬性值并結合使用MessageFormat就可以滿足要求了。
上面的例子中,我們僅向使用者提供一般性問候,下面我們對資源檔案進行改造,通過格式化串讓問候語更具個性化:
greeting.common=How are you!{0},today is {1}
greeting.morning = Good morning!{0},now is {1 time short}
greeting.afternoon = Good Afternoon!{0} now is {1 date long}
将該資源檔案儲存在fmt_resource_en_US.properties中,按照同樣的方式編寫對應的中文本地化資源檔案fmt_resource_zh_CN.properties。
下面,我們聯合使用ResourceBoundle和MessageFormat得到美國英文的本地化問候語:
//①加載本地化資源
ResourceBundle rb1 =
ResourceBundle.getBundle("com/baobaotao/i18n/fmt_ resource",Locale.US);
ResourceBundle rb2 =
ResourceBundle.getBundle("com/baobaotao/i18n/fmt_ resource",Locale.CHINA);
Object[] params = {"John", new GregorianCalendar().getTime()};
String str1 = new MessageFormat(rb1.getString("greeting.common"),Locale. US).format(params); ②
String str2 =new MessageFormat(rb2.getString("greeting.morning"),Locale. CHINA).format(params);
String str3 =new MessageFormat(rb2.getString("greeting.afternoon"),Locale. CHINA).format(params);
System.out.println(str1);
System.out.println(str2);
System.out.println(str3);
運作以上的代碼,将輸出以下資訊:
How are you!John,today is 1/9/07 4:11 PM
早上好!John,現在是下午4:11
下午好!John,現在是2007年1月9日