# 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"));
}
}
```

與 .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"));
}
}
```
印出結果:

光短短兩行就有兩個小問題發生,第一個問題是,明明有兩個 .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
```

這是 .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 並沒有改成加拿大,而且默認也不是檢索中文資源包。

:::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"));
}
}
```
結果是都有返回值

## 結論
ResourceBundle 包含了開發多語言應用程序所需的大部分功能,上述介紹的功能操作上也非常簡單。這就是網頁或遊戲切換語系的基本原理,簡而言之,ResourceBundle 使我們能夠從包含多種語言的應用程序加載對應的數據。