講講Java的國際化

Java 國際化.png

每個面向國際的語言都需要有自己的國際化解決方案囤屹,Java 從一開始就是用 Unicode 來處理所有的字符串晦鞋,這讓 Java 具備了國際化的一個重要特性肌似。除了提供 Unicode 之外菩帝,Java 還需要解決不同區(qū)域的表示形式过蹂。

Locale

java.util 包中提供了 Locale 類用來處理不同的語言和區(qū)域上的國際化問題纠吴。使用 Locale 類可以定制一個區(qū)域的信息硬鞍,而 Locale 類提供了如下構(gòu)造方法來構(gòu)造 Locale 對象。

public final class Locale implements Cloneable, Serializable {
    // 從語言戴已,國家和變體構(gòu)造語言環(huán)境
    public Locale(String language, String country, String variant) {
        ···
    }
    // 從語言和國家構(gòu)造語言環(huán)境
    public Locale(String language, String country) {
        ···
    }
    // 從語言代碼構(gòu)造語言環(huán)境
    public Locale(String language) {
        ···
    }
}

由構(gòu)造方法可知膳凝,構(gòu)造一個 Locale,可以設(shè)置語言恭陡、國家或地區(qū)蹬音、和變體。語言是由 2 個或 3 個小寫字母表示休玩,例如 zh著淆、ende劫狠。這里可以查看 ISO-639-1 語言代碼標識。

Locale 中預定義了大量的 Locale 語言永部,他們只設(shè)定了語言而沒有設(shè)定國家独泞,如下所示。

// 英語
public static final Locale ENGLISH = createConstant("en", "");
// 法語
public static final Locale FRENCH = createConstant("fr", "");
// 德語
public static final Locale GERMAN = createConstant("de", "");
// 意大利語
public static final Locale ITALIAN = createConstant("it", "");
// 日語
public static final Locale JAPANESE = createConstant("ja", "");
// 韓語
public static final Locale KOREAN = createConstant("ko", "");
// 漢語
public static final Locale CHINESE = createConstant("zh", "");
// 簡體中文
public static final Locale SIMPLIFIED_CHINESE = createConstant("zh", "CN");
// 繁體中文
public static final Locale TRADITIONAL_CHINESE = createConstant("zh", "TW");

國家和地區(qū)也是由 2 個大小字母或 3 個數(shù)字表示苔埋,例如 CN懦砂、USDE。這里可以查看 ISO-3166-1 中的國家或地區(qū)標識组橄。

Locale 中也為各個國家預定義了 Locale 對象荞膘,如下所示。

// 法國
public static final Locale FRANCE = createConstant("fr", "FR");
// 德國
public static final Locale GERMANY = createConstant("de", "DE");
// 意大利
public static final Locale ITALY = createConstant("it", "IT");
// 日本
public static final Locale JAPAN = createConstant("ja", "JP");
// 韓國
public static final Locale KOREA = createConstant("ko", "KR");
// 中國
    public static final Locale CHINA = SIMPLIFIED_CHINESE;
// 中華人名共和國
public static final Locale PRC = SIMPLIFIED_CHINESE;
// 臺灣
public static final Locale TAIWAN = TRADITIONAL_CHINESE;
// 英國
public static final Locale UK = createConstant("en", "GB");
// 美國
public static final Locale US = createConstant("en", "US");
// 加拿大
public static final Locale CANADA = createConstant("en", "CA");
// 法國裔加拿大
public static final Locale CANADA_FRENCH = createConstant("fr", "CA");
// 表示根語言環(huán)境的常量
public static final Locale ROOT = createConstant("", "");

變體玉工,用于指定各種雜項特性羽资,如果有多個變量值,每個變量值都指示其自己的語義遵班,則這些值應按重要性排序屠升,并用下劃線分隔。變體字段區(qū)分大小寫狭郑。

Locale 除了構(gòu)造方法可以構(gòu)造 Locale 對象腹暖,Java SE 7 還提供了 forLanguageTag 靜態(tài)方法來構(gòu)建 Locale 對象,如下所示:

Locale zhChinese = Locale.forLanguageTag("zh-CN");
Locale twChinese = Locale.forLanguageTag("zh-TW");
System.out.println(zhChinese.getDisplayName());
System.out.println(twChinese.getDisplayName());
// 中文(中國)
// 中文(臺灣)

LocalesetDefault 方法可以將更改默認地區(qū)翰萨,并使用 getDefault 方法獲取當前的 Locale 對象微服,如下所示:

System.out.println(Locale.getDefault());
Locale.setDefault(Locale.US);
System.out.println(Locale.getDefault());
// zh_CN
// en-US

Locale 中提供的 getAvailableLocale 靜態(tài)方法會返回由 JVM 所能夠識別的所有 Locale 構(gòu)成的數(shù)組。

ResourceBundle

使用 Locale 設(shè)置具體的語言和區(qū)域后缨历,就可以使用 ResourceBundle 根據(jù)不同的 Locale 對資源進行加載。資源需要放在 resources 資源包中糙麦,并根據(jù)不同 Locale 設(shè)置對應的資源文件辛孵。如下所示,通過 ResourceBundle 類提供的 getBundle 靜態(tài)方法赡磅,來根據(jù) locale 自動綁定對應的資源文件魄缚。

Locale locale = Locale.CHINA;
ResourceBundle bundle = ResourceBundle.getBundle("i18n", locale);

查找一個具體的字符串,可以調(diào)用

String language = bundle.getString("language");

這里的資源文件的名稱使用統(tǒng)一的命名規(guī)則焚廊,然后根據(jù)不同的區(qū)域冶匹,要標識其本地信息的附加部分。例如咆瘟,一個資源包的命名是 i18n嚼隘,則與中文、英文環(huán)境相對應的資源如下圖所示袒餐。

14-1 資源文件存放區(qū)域.png

這里可以把默認資源放在一個沒有后綴 i18n 文件中飞蛹,然后 getBundle 方法定位 i18n_zh_CN 時谤狡,還會繼續(xù)查找文件 i18n_zh 和默認文件 i18n 這兩個文件,如果這些文件存在卧檐,它們在資源層次中會成為 i18n_zh_CN 的父文件墓懂。以后,當查找一個資源時霉囚,如果當前資源文件中沒有找到捕仔,就會去查找其父資源文件。

后綴名為 .properties 的文件中盈罐,屬性是采用 = 分隔的鍵值對的形式榜跌,如下所示:

language=中文
color=紅色

ResourceBundle 是一個抽象類,如下所示暖呕。

public abstract class ResourceBundle {
    ...
}

繼承了 ResourceBundle 的類有具體的實現(xiàn)類 PropertyResourceBundle 和抽象類 ListResourceBundle斜做。而 PropertyResourceBundle 是用來支持 ResourceBundle 讀取 properties 資源文件的具體子類,但不需要直接使用 PropertyResourceBundle湾揽,ResourceBundle.getBundle 會自動查找相應的屬性文件瓤逼,并創(chuàng)建一個引用它的 PropertyResourceBundle。但是 properties 資源文件提供的值只能是字符串库物。

如果想提供以外的資源霸旗,需要繼承 ListResourceBundle 抽象類并把所有資源放到一個對象數(shù)組中并提供查找功能,如下所示戚揭。

public class I18N extends ListResourceBundle {
    @Override
    protected Object[][] getContents() {
        return new Object[][]{
                {"color", "yellow" }
        };
    }
}
public class I18N_zh extends ListResourceBundle {
    @Override
    protected Object[][] getContents() {
        return new Object[][]{
                {"language", new String[]{"簡體中文", "繁體中文"}}
        };
    }
}
public class I18N_zh_CN extends ListResourceBundle {
    @Override
    protected Object[][] getContents() {
        return new Object[][]{
                {"language", "簡體中文"},
                {"color", new String[]{"紅色", "黃色", "黑色"} }
        };
    }
}

這里的類的命名規(guī)則也使用標準命名規(guī)則來命名诱告。然后使用 getBundle 方法來加載對應的類:

ResourceBundle bundle = ResourceBundle.getBundle("I18N", Locale.forLanguageTag("zh-CN"));
System.out.println(bundle.getString("language"));
System.out.println(Arrays.toString(bundle.getStringArray("color")));

除此之外,資源包類也可以繼承 ResourceBundle 類進行擴展民晒,但是需要實現(xiàn)兩個方法精居,一是枚舉所有鍵,二是用給定的鍵查找相應的值:

Enumeration<String> getKeys();
Object handleGetObject(String key);

ResourceBundle 類的 getObject 方法會調(diào)用你提供的 handleGetObject 方法潜必。

NumberFormat

java.text 包中的 NumberFormat 可以根據(jù)不同的 Locale 對數(shù)值進行格式化和解析靴姿。

使用 getNumberInstance 靜態(tài)方法獲取對數(shù)字進行格式化和解析的實例,然后將對應的數(shù)字進行格式化磁滚,看相應的 Locale 實例對應的格式化如何不同佛吓。

NumberFormat numberFormat = NumberFormat.getNumberInstance(Locale.SIMPLIFIED_CHINESE);
String result = numberFormat.format(2021.0731);
System.out.println(result);

結(jié)果是 2,021.073,更換成美國垂攘、德國等的結(jié)果分別是 2,021.0732.021,073维雇。當數(shù)字值為貨幣的時候,使用 getCurrencyInstance 靜態(tài)方法晒他,如下所示吱型。

NumberFormat currencyFormat = NumberFormat.getCurrencyInstance(Locale.SIMPLIFIED_CHINESE);
String result = currencyFormat.format(2021.0731);
System.out.println(result);

結(jié)果是 ¥2,021.07,更換成美國陨仅、德國等的結(jié)果分別是 $2,021.072.021,07 €唁影。

處理貨幣時耕陷,可以使用 Currency 類來控制貨幣,可以通過 Currency.getInstance 靜態(tài)方法傳入一個貨幣標識并返回 Currency 對象据沈,然后調(diào)用 NumberFormat 中的 setCurrency 方法哟沫。下面的例子是給德國用戶設(shè)置人民幣的格式,如下所示锌介。

NumberFormat currencyFormat = NumberFormat.getCurrencyInstance(Locale.SIMPLIFIED_CHINESE);
currencyFormat.setCurrency(Currency.getInstance("CNY"));
String result = currencyFormat.format(2021.0731);
System.out.println(result);    // 2.021,07 CN¥

貨幣標識符由 ISO-4217 定義嗜诀,下面就簡單的介紹幾種貨幣標識符。

貨幣值 標識符 貨幣值 標識符
U.S.Dollar USD Chinese Renminbi(Yuan) CNY
Euro EUR Indian Rupee INR
British Pound GBP Russian Ruble RUB
Japanese Yen JPY

DateTimeFormatter

每個國家和地區(qū)對日期和時間的顯示都不可能相同孔祸,Java SE 8 提供了 java.time.DateTimeFormatter 類來處理與 Locale 相關(guān)的日期和時間隆敢,格式化和解析出符合本地日期和時間的表示方法。如下所示崔慧,使用 DateTimeFormatter 中的 withLocale 設(shè)置不同的 Locale拂蝎。

DateTimeFormatter dateFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL).withLocale(Locale.SIMPLIFIED_CHINESE);

之后,就可以格式化 LocaleDate惶室、LocaleDateTime温自、LocaleTimeZonedDateTime 了。

ZonedDateTime appointment = ZonedDateTime.now();
String formatted = formatter.format(appointment);

輸出的結(jié)果是 2021年7月31日星期六皇钞,更換成美國悼泌、德國的結(jié)果分別是 Saturday, July 31, 2021Samstag, 31. Juli 2021。輸出一個地區(qū)的第一天時夹界,可以通過 Locale 設(shè)置馆里,如下所示。

DayOfWeek first = WeekFields.of(locale).getFirstDayOfWeek();

MessageFormat

一段消息中也會根據(jù)不同的 Locale 進行變更的可變數(shù)據(jù)可柿,Java 提供了 java.text.MessageFormat 來對消息進行格式化鸠踪。如下一段話:

String msg = "On {2}, {0} destroyed {1} houses and caused {3} of damage."

括號中的數(shù)字是占位符,可以通過 MessageFormat.format 靜態(tài)方法使用實際的值來替換占位符复斥。它是一個 varargs 方法营密,所以可以通過下面的方法提供參數(shù):

String result = MessageFormat.format(msg, "a hurricane", 99, new GregorianCalendar(1999, 0, 1).getTime(), 10.0E8);

輸出結(jié)果是:

On 1/1/99 12:00 AM, a hurricane destroyed 99 houses and caused 100,000,000 of damage.

上面的例子還可以為占位符提供可選的格式,將打印的內(nèi)容變得更為精細永票。

On {2, date, long}, {0} destroyed {1} houses and caused {3, number, currency} of damage.

輸出結(jié)果是:

On January 1, 1999, a hurricane destroyed 99 houses and caused $100,000,000 of damage.

一般來說,占位符索引后面可以跟一個 type 和一個 style滥沫,它們之間用逗號隔開侣集。

MessageFormat.format 靜態(tài)方法使用當前的 Locale 對值進行格式化。要想用任意的 Locale 進行格式化兰绣,需要為這個類提供可以使用的 varargs 方法世分。你需要把將要格式化的值置于 Object[] 數(shù)組中,如下所示缀辩。

MessageFormat mf = new MessageFormat(pattern, locale);
String msg = mf.format(new Object[] {values});

歡迎關(guān)注公眾號「海人為記」臭埋,期待與你共同進步踪央!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市瓢阴,隨后出現(xiàn)的幾起案子畅蹂,更是在濱河造成了極大的恐慌,老刑警劉巖荣恐,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件液斜,死亡現(xiàn)場離奇詭異,居然都是意外死亡叠穆,警方通過查閱死者的電腦和手機少漆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來硼被,“玉大人示损,你說我怎么就攤上這事∪铝颍” “怎么了检访?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長论巍。 經(jīng)常有香客問我烛谊,道長,這世上最難降的妖魔是什么嘉汰? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任丹禀,我火速辦了婚禮,結(jié)果婚禮上鞋怀,老公的妹妹穿的比我還像新娘双泪。我一直安慰自己,他們只是感情好密似,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布焙矛。 她就那樣靜靜地躺著,像睡著了一般残腌。 火紅的嫁衣襯著肌膚如雪村斟。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天抛猫,我揣著相機與錄音蟆盹,去河邊找鬼。 笑死闺金,一個胖子當著我的面吹牛逾滥,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播败匹,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼寨昙,長吁一口氣:“原來是場噩夢啊……” “哼讥巡!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起舔哪,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤困檩,失蹤者是張志新(化名)和其女友劉穎渔肩,沒想到半個月后码泞,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體逛薇,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年外里,在試婚紗的時候發(fā)現(xiàn)自己被綠了怎爵。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡盅蝗,死狀恐怖鳖链,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情墩莫,我是刑警寧澤芙委,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站狂秦,受9級特大地震影響灌侣,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜裂问,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一侧啼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧堪簿,春花似錦痊乾、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至虑瀑,卻和暖如春湿滓,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背舌狗。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工叽奥, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人把夸。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓而线,卻偏偏與公主長得像铭污,于是被迫代替她去往敵國和親恋日。 傳聞我的和親對象是個殘疾皇子膀篮,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內(nèi)容