# ResourceBundle 基本應用 [TOC] ## ResourceBundle 介紹及如何寫 .properties 如果程式碼的字串寫死,若要改成其他文字,那你就要重新編譯才有辦法修改,如下: ```java public static void main(String[] args) { System.out.println("Hello!World!"); } ``` 如果需求說要改成中文那麼只能改成: ```java public static void main(String[] args) { System.out.println("哈囉!世界!"); } ``` 這種修改方式相當不便,要修改時,你的程式碼一定要重新編譯,對於像這樣的情境下,我們有一個專屬的方法可以使用,java.util.ResourceBundle 用來做訊息綁定,首先要建立 .properties 的檔案,官方文檔有提到這些 .properties 必須都放在同一目錄中,並且可共享一樣的 baseName,什麼意思呢?就是 baseName 後可以加上語系、國家及平台的後綴,如下: * ExampleResource * ExampleResource_en * ExampleResource_zh_TW * ExampleResource_zh_CN_UNIX 那 messages_en_US.properties 要如何編寫?檔案內容如下: ```haskell #我是註解 test.hello.message=Hello test.goodbye.message=Goodbye !我也是註解 ``` 是以 Key-Value 形式儲存的數據,Key 值有區分大小寫,也可以在檔案中使用註解,註解時 # 與 ! 須寫在開頭。 也可以使用 ListResourceBundle ,首先要 extends ListResourceBundle,並且 @Override getContents() 方法,示例如下: ```java import java.util.ListResourceBundle; class MyResources extends ListResourceBundle { protected Object[][] getContents() { return new Object[][]{ {"hello_en", "Hello World!"}, {"hello_tw", "哈囉 世界!"}, {"PI", 3.14159} }; } } ``` ```java import java.util.ResourceBundle; public class ResourceTest { public static void main(String[] args) { //儲存特定語言的資源,是ListResourceBundle的擴展 MyResources mr = new MyResources(); System.out.println(mr.getString("hello_en")); System.out.println(mr.getString("hello_tw")); System.out.println(mr.getObject("PI")); } } ``` ![](https://i.imgur.com/oY3vY1P.png) 與 .properties 相比,ListResourceBundle 有一個主要的優勢,它是可以保存任何對象,不僅僅是上述的字串,但缺點也很明顯,因為是 java 類,每次修改都需要重新編譯。 ## 依 .properties 使用 ResourceBundle 那麼要如何依 .properties 使用 ResourceBundle 呢?剛剛已經有建立好一個 messages_en_US.properties 了,我們再建立一個 messages_zh_TW.properties,檔案內容如下: ```haskell test.hello.message=哈囉 ``` 目前我們有 messages_en_US.properties 及 messages_zh_TW.properties,接下來用 Key 去取出對應的值,程式碼如下: ```java import java.util.ResourceBundle; public class ResourceTest { public static void main(String[] args) { //獲取靜態資源包並輸出指定鍵值 ResourceBundle res = ResourceBundle.getBundle("messages"); System.out.println(res.getString("test.hello.message")); } } ``` 印出結果: ![](https://i.imgur.com/2mExPU6.png) 光短短兩行就有兩個小問題發生,第一個問題是,明明有兩個 .properties 它是怎麼選擇是讀取 messages_en_US.properties 還是 messages_zh_TW.properties,第二,印出來怎麼是亂碼?首先我們來解決第一個問題,在默認的情況下,有多種不同語系的 .properties 時,它選擇的先後順序是依 JVM 語言環境而選,所以在獲取 .properties 時,它會去找 messages_zh_TW.properties,第二個問題則是 .properties 官方敘述說,它的編碼是使用 [ISO 8859-1](https://zh.wikipedia.org/wiki/ISO/IEC_8859-1) 編碼,該編碼的字符少於 UTF-8,導致中文顯示出現問題,解決的方法就是將中文轉成 [Unicode](https://zh.wikipedia.org/wiki/Unicode),示例如下: ```haskell test.hello.message=\u54c8\u56c9 ``` ![](https://i.imgur.com/if5874j.png) 這是 .properties 的一個缺點,而 ListResourceBundles 是 java 類,因此它能保存 UTF-16 編碼支持的任何字元。 補充:[Unicode 編碼轉換工具](https://www.ifreesite.com/unicode-ascii-ansi.htm) :::warning **notice :** 在 java9 之後,.properties 就支援 UTF-8 了,也就不需要再轉 Unicode。 ::: ## ResourceBundle 客製化 上述有提到過,ResourceBundle.getBundle 取得的靜態資源包(properties)默認是中文,想要默認是英文也是有辦法的。首先 extends ResourceBoundle.Control,並 @Override 其方法,我們要客製的是默認語言取得資源包,程式碼如下: ```java import java.util.Arrays; import java.util.List; import java.util.Locale; import java.util.ResourceBundle; public class ExampleControl extends ResourceBundle.Control { ExampleControl() {//Control 要先初始化,初始化繼承父類 super(); } @Override public List<Locale> getCandidateLocales(String s, Locale locale) { return Arrays.asList(new Locale("en", "US"));//改成默認 Locale 為英文 } } ``` 客製 ResourceBundle 的控制器,測試將檢索資源包條件改為 Locale.CANADA 後是否會去找加拿大的資源包,程式碼如下: ```java import java.util.Locale; import java.util.ResourceBundle; public class ResourceTest { public static void main(String[] args) { ExampleControl rbc = new ExampleControl();//自訂控制器 System.out.println(rbc.getCandidateLocales("message", Locale.CANADA)); ResourceBundle example = ResourceBundle.getBundle("messages", rbc); System.out.println(example.getString("test.goodbye.message")); } } ``` 結果如下,我們看到它 Locale 並沒有改成加拿大,而且默認也不是檢索中文資源包。 ![](https://i.imgur.com/HGNp5ex.png) :::warning **notice :** ResourceBundle.getBundle(String baseName, Control control) 被 static 和 final 所修飾,意思是若要用同個 baseName 呼叫 getBundle 去檢索資源包,都會依照第一次檢索到的資源包為準,因為它具有唯一且不可改的特性。 ::: ## ResourceBundle 繼承 最後補充一點,.properties 還有一個特性,它是可以繼承的,以下建立三個 .properties: ```haskell #messages.properties test.gg.message=GG #messages_zh.properties test.goodbye.message=\u518d\u898b #messages_zh_TW.properties test.hello.message=\u54c8\u56c9 ``` 我們依 baseName 為 messages、Locale 為 Locale("zh","TW") 檢索 .properties,並試著獲取三個 .properties 的鍵值,程式碼如下: ```java import java.util.Locale; import java.util.ResourceBundle; public class ResourceTest { public static void main(String[] args) { ResourceBundle res = ResourceBundle.getBundle("messages",new Locale("zh","TW")); System.out.println(res.getString("test.hello.message")); System.out.println(res.getString("test.goodbye.message")); System.out.println(res.getString("test.gg.message")); } } ``` 結果是都有返回值 ![](https://i.imgur.com/v1r1YqZ.png) ## 結論 ResourceBundle 包含了開發多語言應用程序所需的大部分功能,上述介紹的功能操作上也非常簡單。這就是網頁或遊戲切換語系的基本原理,簡而言之,ResourceBundle 使我們能夠從包含多種語言的應用程序加載對應的數據。