關於 ResourceBundle


在同一個頁面中,如果要同時顯示正體中文、簡體中文甚至日文,當然不是直接在頁面上直接鍵入正體中文、簡體中文甚至日文,方法之一是使用 HTML 實體編號(Entity number),但是,若要同一個頁面可依使用者選擇全面切換正體中文、簡體中文甚至日文,就不是使用HTML實體編號那麼單純。

來看看 Java 中的作法,這必須從 ResourceBundle 開始談起。

在程式中有很多字串訊息會被寫死在程式中,如果想要改變某個字串訊息,必須修改程式碼然後重新編譯,例如簡單顯示 "Hello!World!" 的程式就是如此:

public class Hello {
     public static void main(String[] args) {
         System.out.println("Hello!World!");
     }
} 

就這個程式來說,如果日後想要改變 "Hello!World!""Hello!Java!",就要修改程式碼中的文字訊息並重新編譯。

對於日後可能變動的文字訊息,可以考慮將訊息移至程式之外,方法是使用 java.util.ResourceBundle 來作訊息綁定,首先要先準備一個 .properties 檔案,例如 messages.properties,而檔案內容如下:

cc.openhome.welcome=Hello
cc.openhome.name=World 

.properties 檔案必須放置在 Classpath 的路徑設定下,檔案中撰寫的是鍵(Key)、值(Value)配對,之後在程式中可以使用鍵來取得對應的值,例如:

import java.util.ResourceBundle;
public class Hello {
    public static void main(String[] args) {
        ResourceBundle res = ResourceBundle.getBundle("messages");
        System.out.print(res.getString("cc.openhome.welcome") + "!");
        System.out.println(res.getString("cc.openhome.name") + "!");
    }
} 

ResourceBundle 的靜態 getBundle() 方法會取得一個 ResourceBundle 的實例,所給定的引數名稱是訊息檔案的主檔名,getBundle() 會自動找到對應的 .properties檔案,取得 ResourceBundle 實例後,可以使用 getString() 指定鍵來取得檔案中對應的值,如果日後想要改變顯示的訊息,只要改變 .properties 檔案的內容就可以了。

既然可以指定資源檔來載入訊息,那如果準備各種不同語系的資源檔,依使用者的選項來切換資源檔案的選擇,不就可以全面切換頁面中的語系顯示了嗎?

如果一個應用程式在設計時,可以在不修改應用程式的情況下,根據不同的使用者直接採用不同的語言、數字格式、日期格式等,這樣的設計考量稱為國際化(internationalization),簡稱 i18n(因為 internationalization 有 18 個字母)。

國際化的三個重要觀念是地區(Locale)資訊、資源包(Resource bundle)與基礎名稱(Base name)。

地區資訊代表了特定的地理、政治或文化區,地區資訊可由一個語言編碥(Language code)與可選的地區編碼(Country code)來指定,其中語言編碼是 ISO-639 定義,由兩個小寫字母代表,例如 "fr" 表示法文(French),"zh" 表示中文(Chinese)。地區編碼則由兩個大寫字母表示,定義在 ISO-3166,例如 IT 表示義大利(Italy)、TW 表示台灣(Taiwan)。

地區(Locale)資訊的對應類別 Locale,在建立 Locale 時,可以指定語言編碼與地區編碼,例如建立代表台灣繁體中文的 Locale,可以如下:

Locale locale = new Locale("zh", "TW");

資源包中包括了特定地區的相關資訊,先前所介紹的 ResourceBundle 物件,就是 JVM 中資源包的代表物件。代表同一組訊息但不同地區的各個資源包共用相同的基礎名稱。

使用 ResourceBundlegetBundle() 時指定的名稱,就是在指定基礎名稱。例如,ResourceBundlegetBundle() 時若指定 "messages",則嘗試用預設的 Locale(由 Locale.getDefault() 取得的值)取得 .properties 檔案。

例如,若預設的 Locale 代表 zh_TW,則 ResourceBundlegetBundle() 時若指定 "messages",則會嘗試取得 messages_zh_TW.properties 檔案中的訊息,若找不到,再嘗試找 messages.properties 檔案中的訊息。

在〈Java 的字串〉談過 Java 中字串的處理,如果希望用建立一個 messages_zh_TW.properties,並在當中建立台灣繁體中文的訊息,在 Java 8 或早期版本中,並非直接在 messages_zh_TW.properties 中撰寫中文,而是必須使用 Unicode 碼點表示,這可以透過 JDK 工具程式 native2ascii 來協助轉換。例如,可以在 messages_zh_TW.txt 中撰寫以下內容:

cc.openhome.welcome=哈囉
cc.openhome.name=世界 

如果編輯器使用 Big5 編碼,那麼你可以如下執行 native2ascii 程式:

native2ascii -encoding Big5 messages_zh_TW.txt messages_zh_TW.properties

如此就會產生 messages_zh_TW.properties 檔案,內容如下:

cc.openhome.welcome=\u54c8\u56c9
cc.openhome.name=\u4e16\u754c 

也就是 native2ascii 程式會將非 ASCII 字元轉換為 Unicode 編碼表示,如果想將 Unicode 編碼表示的 .properties 轉回中文,則可以使用 -reverse 引數,例如將上面的程式轉回中文,並使用 UTF-8 編碼檔案儲存:

native2ascii -reverse -encoding UTF-8 messages_zh_TW.properties messages_zh_TW.txt

從 Java 9 開始,支援 UTF-8 的 .properties,撰寫中文後只要使用 UTF-8 儲存,不需要再透過 native2ascii 的轉換(JDK9 中也沒有這個指令了)。

如果執行先前的 Hello 類別,而系統預設 Localezh_TW,則會顯示 "哈囉!世界!" 的結果。如果提供messages_en_US.properties:

cc.openhome.welcome=Hello
cc.openhome.name=World 

ResourceBundlegetBundle() 可以指定 Locale 物件,如果如下撰寫程式:

Locale locale = new Locale("en", "US");
ResourceBundle res = ResourceBundle.getBundle("messages", locale);
System.out.print(res.getString("cc.openhome.welcome") + "!");
System.out.println(res.getString("cc.openhome.name") + "!");

ResourceBundle 會嘗試取得 messages_en_US.properties 中的訊息,結果就是顯示 "Hello!World!"

置換語系的基本原理就是如此,有些程式庫可以協助簡化這個流程,例如 JSTL 的 I18N 相容格式標籤庫,這之後再說明。