博客 / 詳情

返回

國際化(i18n)

國際化(i18n)

源: https://blog.ximinghui.org/e9b09f41/index.html

以 Java 21 為例,簡單探索i18n。

一、什麼是國際化 / i18n?

國際化是中文名,i18n是國際化英文單詞“internationalization”的縮寫,因為第一個字母i和最後一個字母n中間有18個字母。

應用程序國際化就是應用程序適應不同的地區/國家和語言。比如中國人打開軟件看到的就是簡體中文和符合中國人習慣格式,法國人看到的則是法語和符合他們習慣的格式。

二、地區/國家和語言

因為不同國家對某些地方是否屬於獨立國家的認同不一致,這裏提醒儘量不用“國家(Country)”而是使用“地區(Region/Locale)”稱呼來避免帶來不必要的麻煩。本文將使用“地區”來稱呼。

i18n主要以“地區”+“語言”為基礎來進行適配。例子如“簡體中文,新加坡”、“英文,香港”。

語言和地區更多信息見“Java中的語言和地區標準”。

三、嘗試使用i18n打印本地化的歡迎語

小提示:可以使用更加符合本土習慣的“翻譯”而不是機械地直譯。比如,西方人看到的問候語是“Hey there! You look very smart today.”,台灣人看到的是“好久不見,歡迎回來!”,藏族人看到的是“བཀྲ་ཤིས་བདེ་ལེགས(大意為‘祝你好運’或‘願所有吉祥的徵兆到來’)”。相反,生硬的直譯如某軟:“請坐和放寬,好東西就要來了。”。

1. 創建i18n項目

項目結構:

i18n
 |- pom.xml
 |- src
     |- main
         |- java
             |- org.ximinghui
                 |- Test.java
         |- resource

pom.xml內容:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <!-- 項目信息 -->
    <groupId>org.ximinghui</groupId>
    <artifactId>i18n</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!-- 屬性定義 -->
    <properties>
        <!-- Java源代碼版本 -->
        <maven.compiler.source>21</maven.compiler.source>
        <!-- Java字節碼版本 -->
        <maven.compiler.target>21</maven.compiler.target>
        <!-- Java發行版本 -->
        <maven.compiler.release>21</maven.compiler.release>
        <!-- Java源碼和資源文件編碼 -->
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <!-- 依賴項 -->
    <dependencies>
        <!-- lombok:提供自動化管理類構造器、Setter方法、Getter方法、日誌對象等能力的Java類管理庫 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.34</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

</project>

Test.java內容:

package org.ximinghui.test;

public class Test {

    public static void main(String[] args) {
    }

}

2. 體驗i18n

提示:這裏使用Java標準的i18n而不是Spring的i18n。

創建資源捆:在resource目錄創建messages.propertiesmessages_zh_CN.propertiesmessages_zh_TW.properties文件。

提示:properties文件編碼為 iso 8859-1,且不支持Unicode(即非ASCII字符),但自Java 9開始,用於國際化的properties文件支持UTF-8且默認為UTF-8。需要注意的是,僅指用於i18n的properties文件。Properties類保持ISO 8859-1不變),而i18n文件讀取是由PropertyResourceBundle類完成的且該類默認UTF-8)。

messages.properties內容:

greetings=Hey there! You look very smart today.

messages_zh_CN.properties內容:

greetings=看,星星在朝你眨眼,一切美好如你所願。

messages_zh_TW.properties內容:

greetings=好久不見,歡迎回來!

提示:請確保IDE/編輯器支持UTF-8的properties文件編輯。常見的如 IntelliJ IDEA 默認強制properties文件使用ISO 8859-1且限制修改編碼,應在Settings->Editor->File Encodings中更改(更改後請注意確保非i18n的properties文件依然為8859-1編碼)。

打印問候語:

public static void main(String[] args) {
    // 獲取資源捆
    ResourceBundle chinaResourceBundle = ResourceBundle.getBundle("messages", Locale.CHINA);
    // 獲取國際化的問候語
    String greetings = chinaResourceBundle.getString("greetings");
    // 打印問候語:看,星星在朝你眨眼,一切美好如你所願。
    System.out.println(greetings);
}

調整上述代碼的Locale.CHINALocale.TAIWAN,再次運行則打印:“好久不見,歡迎回來!”

3. 解釋

a. 資源捆(ResourceBundle)

資源捆是一組資源文件,比如語言包、圖像或配置文件。上面的 messages.propertiesmessages_zh_CN.propertiesmessages_zh_TW.properties 文件就是一個名為“message”的資源捆,其後跟上語言和地區後綴。資源捆名字根據情況自行命名。

每個properties文件中都由一行行的鍵值對組成,key用來定位消息,value則為對應的本地化消息。

key的命名沒有固定標準,根據團隊情況保持統一就好,常見的如全字母小寫 display.button.create=創建,或者全小寫加下劃線 display.button_text.save_change=Save change,或Spring Security Core中的風格AbstractUserDetailsAuthenticationProvider.credentialsExpired=User credentials have expired

語言和地區後綴部分解釋見“Java中的語言和地區標準”。

b. 代碼

代碼ResourceBundle.getBundle("messages", Locale.CHINA)獲取一個名為message,地區為CHINA的資源捆。

代碼chinaResourceBundle.getString("greetings")獲取鍵 “greetings” 對應的消息。

提示:具體傳遞的Locale對象,服務器端可以提供根據請求的Accept-Language相關標頭來決定。或者應用程序也可以在數據庫或前端存儲用户時區、語言、日期時間格式等偏好信息,服務器根據存儲的偏好來決定。或者以用户設備系統的當前所在地區來決定等等。

四、Java中的語言和地區標準

語言和地區是多對多的關係。一語言可以在多個地區存在,美國、新加坡、香港等也有簡體中文;一地區也有多種語言,如中國除了簡體中文還有蒙文、藏文、維吾爾文和壯文(字樣見紙質人民幣)。

JDK的Locale類中只定義了常見的語言地區,如果我們要支持小眾些的語言,就需要自定義Locale了。

java.util.Locale的Javadoc文檔有許多説明,這裏簡單説下兩個部分:

1. language

language應是表示語言的代碼,可以是ISO 639 alpha-2 語言代碼ISO 639 alpha-3 語言代碼IANA 註冊的語言子標籤

這裏以IANA 註冊的語言子標籤為例,其中Type為language的都是可用的語言代碼,取Subtag即可。如Type: language & Subtag: zh

提示:可以藉助 https://glosbe.com/zh 網站查詢語言信息,將url路徑中的zh改成想要查詢的Subtag值,比如查詢藏語:https://glosbe.com/bo。該網站還可以鏈接到查詢語言對應的維基百科和 Ethnologue站點(全球語言信息在線數據庫,提供語言使用情況、分佈、方言、相關的文化、歷史背景等信息)。

提示:維基百科 - ISO_639:m頁面也可用於語言Subtag的對照。

2. country / region

country / region應是ISO 3166 alpha-2 國家代碼或 UN M.49 數字-3 區域代碼。同樣可以查詢IANA 註冊的語言子標籤,但是要看類型為地區的,即Type: region

五、嘗試添加藏語支持

參考IANA 註冊的語言子標籤,找到或Google蒐藏語的Subtag / Language code,得知藏語為 bo。

有了語言,還得有地區。藏語使用主要分佈在中國西藏自治區,但西藏並沒有像港澳台一樣有自己的Region子標籤,所以區域上應選中國,即藏語, 中國。現屬印度的錫金或一些別的地放(主要是喜馬拉雅地區)也有使用藏語的藏族,因此可以創建 藏語, 印度來對應印度的藏語。

於是我們就可以構建出藏語Locale對象:

// 藏語, 中國
Locale chinaTibetan = new Locale.Builder().setLanguage("bo").setRegion("CN").build();
// 藏語, 印度
Locale indiaTibetan = new Locale.Builder().setLanguage("bo").setRegion("IN").build();

添加對應的資源文件:

messages_bo_CN.properties內容:

greetings=བཀྲ་ཤིས་བདེ་ལེགས

messages_bo_IN.properties內容:

greetings=བཀྲ་ཤིས་བདེ་ལེགས

傳遞自定義的Locale對象來打印對應的問候語:

String chinaTibetanGreetings = ResourceBundle.getBundle("messages", chinaTibetan).getString("greetings");
String indiaTibetanGreetings = ResourceBundle.getBundle("messages", indiaTibetan).getString("greetings");

System.out.println(chinaTibetanGreetings);
System.out.println(indiaTibetanGreetings);

六、默認資源文件

在"message"資源捆中,資源文件清單如下:

  • messages.properties
  • messages_bo_CN.properties
  • messages_bo_IN.properties
  • messages_zh_CN.properties
  • messages_zh_TW.properties

除了帶語言和地區的文件外,還有message.propertires這樣的後面什麼都不帶的文件,這就是默認資源文件,用於沒有提供/找不到語言或地區時作為通用消息。

演示:

// 隨意創建一個不存在的語言代碼
Locale unsupportedLocale = new Locale.Builder().setLanguage("ij").setRegion("CN").build();

// 獲取國際化消息
String greetings = ResourceBundle.getBundle("messages", unsupportedLocale).getString("greetings");

// 由於找不到指定的資源文件,故回退到默認資源文件
// 輸出:Hey there! You look very smart today.
System.out.println(greetings);

七、嘗試枚舉值國際化

創建性別枚舉類:

@AllArgsConstructor
public enum PersonSex {

    MALE("display.sex.male"),

    FEMALE("display.sex.female"),

    UNKNOWN("display.sex.unknown");

    private final String i18nKey;

    /**
     * 獲取性別展示名,默認區域為China
     *
     * @return 性別展示名
     */
    public String displayName() {
        return displayName(Locale.CHINA);
    }

    /**
     * 獲取國際化的性別展示名
     *
     * @param locale 語言環境,即展示名稱的語言
     * @return 性別展示名
     */
    public String displayName(Locale locale) {
        ResourceBundle bundle = ResourceBundle.getBundle("messages", locale);
        return bundle.getString(i18nKey);
    }

}

在所有資源文件中添加 display.sex.male 等鍵值對。以messages_zh_CN.properties為例(其他文件省略):

greetings=看,星星在朝你眨眼,一切美好如你所願。
display.sex.male=男
display.sex.female=女
display.sex.unknown=未知

測試:

public static void main(String[] args) {
    System.out.println(PersonSex.MALE.displayName(Locale.TAIWAN));
}
user avatar dm2box 頭像 docker_app 頭像 yadong_zhang 頭像 redorblack 頭像 knifeblade 頭像 buxiyan 頭像
6 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.