在同一個頁面中,如果要同時顯示正體中文、簡體中文甚至日文,當然不是直接在頁面上直接鍵入正體中文、簡體中文甚至日文,方法之一是使用 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 中資源包的代表物件。代表同一組訊息但不同地區的各個資源包共用相同的基礎名稱。
使用 ResourceBundle
的 getBundle()
時指定的名稱,就是在指定基礎名稱。例如,ResourceBundle
的 getBundle()
時若指定 "messages"
,則嘗試用預設的 Locale
(由 Locale.getDefault()
取得的值)取得 .properties 檔案。
例如,若預設的 Locale
代表 zh_TW
,則 ResourceBundle
的 getBundle()
時若指定 "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
類別,而系統預設 Locale
為 zh_TW
,則會顯示 "哈囉!世界!"
的結果。如果提供messages_en_US.properties:
cc.openhome.welcome=Hello
cc.openhome.name=World
ResourceBundle
的 getBundle()
可以指定 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 相容格式標籤庫,這之後再說明。