天天看點

Java國際化/本地化實戰(上)0 前言1 簡介2 Locale 類本地化工具類資源檔案/屬性檔案

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标準定義的,每個國家/地區由兩個大寫字母表示

Java國際化/本地化實戰(上)0 前言1 簡介2 Locale 類本地化工具類資源檔案/屬性檔案

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日