網(wǎng)站國際化實(shí)現(xiàn)(1)—JDK的國際化支持

一、背景

很多網(wǎng)站的用戶分布在世界各地仰冠,因此網(wǎng)站需要針對(duì)不同國家的用戶展示不同語言的內(nèi)容乏冀,因此就有了國際化實(shí)現(xiàn)的需求,大多數(shù)網(wǎng)站都會(huì)在網(wǎng)站的頭部或尾部設(shè)置語言切換鏈接辆沦,這樣就可以直接切換成相應(yīng)的內(nèi)容蔚晨。其中有些網(wǎng)站是通過網(wǎng)站地址或參數(shù)進(jìn)行區(qū)分累舷,有些是通過設(shè)置cookie值進(jìn)行進(jìn)行區(qū)分析蝴。

這里先不講網(wǎng)站具體的實(shí)現(xiàn)佑菩,先介紹下網(wǎng)站國際化需要的基礎(chǔ)知識(shí),即JDK本身對(duì)國際化的支持谭确。這里說明下JDK本身的國際化只是網(wǎng)站國際化實(shí)現(xiàn)的基礎(chǔ)禀梳,其本身還可以支持GUI程序或其它應(yīng)用程序的國際化實(shí)現(xiàn)郊艘。

二、簡介

JAVA官方國際化教程

國際化(Internationalization )用于便捷地支持不同語言或區(qū)域的處理唯咬,國際化有時(shí)簡稱為 i18n纱注,取Internationalization單詞的首字母和尾字母,中間因?yàn)檫€有18個(gè)字母胆胰,用18代替狞贱,故簡寫為i18n。

一般需要國際化處理的數(shù)據(jù)有時(shí)間蜀涨、數(shù)字瞎嬉、金額、文本等厚柳。國際化一般有本地化的數(shù)據(jù)氧枣,而且通常都不是硬編碼的,不需要每次修改都重新編譯别垮,而且還需要處理非常便捷便监。

國際化的整個(gè)過程可以大致分為三步:本地化、數(shù)據(jù)獲取、格式化烧董。下面再詳細(xì)說明下毁靶。

三、本地化

既然要做到國際化逊移,那么首先肯定得知道是哪個(gè)語言或區(qū)域预吆,這個(gè)如何去獲取或設(shè)置呢?JDK提供了Locale類去抽象本地化實(shí)現(xiàn)胳泉,Locale對(duì)象表示了特定的地理啡浊、政治和文化地區(qū)。

Locale有幾個(gè)重要的編碼這里先介紹下:

  1. 語言編碼(Language Code): 兩到三位符合ISO 639 標(biāo)準(zhǔn)的字母胶背。這個(gè)編碼比較好理解,主要用作不同語言的定義喘先。語言編碼參照表鏈接
  2. 腳本編碼(Script Code):由一個(gè)大寫首字母+三個(gè)小寫字母組成钳吟,符合ISO 15924標(biāo)準(zhǔn)的編碼。這個(gè)編碼JDK7以后才引入窘拯,主要用于區(qū)分同一語言同一國家地區(qū)使用不同的書寫系統(tǒng)的情形红且,例如uz-Cyrl-UZ表示使用西里爾字母的烏茲別克語。腳本編碼參照表鏈接
  3. 區(qū)域編碼(Region Code):由兩個(gè)或者三個(gè)大寫符合ISO 3166標(biāo)準(zhǔn)的字母組成涤姊。 這個(gè)編碼主要用于表示國家或者地區(qū)暇番。區(qū)域編碼參照表鏈接
  4. 多樣編碼(Variant Code):這個(gè)編碼在JDK7以前常用于定義語言或者區(qū)域之外的區(qū)別,比如計(jì)算平臺(tái)Windows或UNIX思喊。但是IETF BCP 47標(biāo)準(zhǔn)不建議這么使用壁酬。所以JDK7之后,多樣編碼(Variant Code)主要用來定義一門語言后者方言的多樣性恨课。多樣編碼參照表鏈接
    而前面說到的非語言的多樣性舆乔,比如平臺(tái)的區(qū)別(Windows, UNIX, Linux)或者發(fā)布信息(6u23 or JDK 7)等,JDK7引入Unicode Locale Extensions支持來符合IETF BCP 47標(biāo)準(zhǔn)剂公。

JDK8支持的本地化一覽鏈接(Supported Locales欄)

當(dāng)然希俩,在實(shí)際Locale使用中可能用不到所有的編碼定義或拓展,大多數(shù)情況下語言編碼和區(qū)域編碼就足夠區(qū)分定義纲辽,不過了解這些編碼的含義與作用對(duì)使用上還是有好處的颜武。實(shí)際上Locale對(duì)象的創(chuàng)建就是根據(jù)上述的編碼和拓展定義出來的。

這里以JDK8為例拖吼,Locale的創(chuàng)建可以通過Locale.Builder類鳞上、Locale本身的構(gòu)造方法、forLanguageTag方法绿贞、或者預(yù)先定義好的常量進(jìn)行創(chuàng)建因块。當(dāng)然getDefault方法也可以得到基于當(dāng)前環(huán)境默認(rèn)的Locale對(duì)象。這里方法上各有差異籍铁,本質(zhì)還是設(shè)置前面說到的編碼或拓展值涡上。

四趾断、數(shù)據(jù)獲取

得到了本地化信息,那么下一步就是要獲取對(duì)應(yīng)的數(shù)據(jù)吩愧。前面提到過國際化需要信息不是硬編碼的芋酌,這樣就不要每次修改都重新編譯,而且也易于維護(hù)雁佳。

在JDK中脐帝,數(shù)據(jù)隔離和獲取一般使用ResourceBundle類配合properties文件使用,實(shí)際使用中糖权,一般會(huì)定義一些properties文件堵腹,文件名前綴相同,后綴跟一些本地化的信息星澳,這樣不同的文件就可以存儲(chǔ)不同本地化對(duì)應(yīng)的數(shù)據(jù)疚顷。

這里說得太抽象,直接上結(jié)合官網(wǎng)示例修改的代碼禁偎,為了便于閱讀腿堤,下面列個(gè)大概,具體請(qǐng)看我上傳的github項(xiàng)目代碼如暖。

<pre><code>public class ResourceBundleDemo {

public static void main(String[] args) {
    // 這里用到的i18n下面的文件名都以下劃線分隔笆檀,RBControl_語言編碼_區(qū)域編碼的形式
    String baseName = "i18n/RBControl";

    // 演示Locale常量解析RBControl_zh_cn.properties數(shù)據(jù)
    Locale l = Locale.CHINA;
    ResourceBundle rs = ResourceBundle.getBundle(baseName, l);
    String result = rs.getString("region");
    System.out.println("示例1結(jié)果:" + result);

    // 演示Locale.Builder解析RBControl_zh_hk.properties數(shù)據(jù)
    l = new Locale.Builder().setLanguage("zh").setRegion("hk").build();
    rs = ResourceBundle.getBundle(baseName, l);
    result = rs.getString("region");
    System.out.println("示例2結(jié)果:" + result);

    // 演示Locale構(gòu)造函數(shù)解析RBControl_zh_tw.properties數(shù)據(jù)
    l = new Locale("zh", "tw");
    rs = ResourceBundle.getBundle(baseName, l);
    result = rs.getString("region");
    System.out.println("示例3結(jié)果:" + result);

    // 演示Locale構(gòu)造函數(shù)解析RBControl_en_US.properties數(shù)據(jù)
    l = Locale.forLanguageTag("en-US");
    rs = ResourceBundle.getBundle(baseName, l);
    result = rs.getString("region");
    System.out.println("示例4結(jié)果:" + result);

    // 演示Locale解析RBControl_zh.properties數(shù)據(jù),但是對(duì)應(yīng)數(shù)據(jù)不存在時(shí),會(huì)取默認(rèn)RBControl.properties
    l = new Locale("zh");
    rs = ResourceBundle.getBundle(baseName, l);
    result = rs.getString("region");
    System.out.println("示例5結(jié)果:" + result);
}

}</pre></code>


Paste_Image.png

對(duì)于ResourceBundle盒至,在指定的locale找不到的時(shí)候酗洒,getBundle方法會(huì)找最相近的
值。例如官網(wǎng)中舉例ButtonLabel_fr_CA_UNIX是文件名枷遂,Locale默認(rèn)是en_US寝蹈,getBundle方法會(huì)按照如下的順序查找ButtonLabel_fr_CA_UNIX、ButtonLabel_fr_CA登淘、ButtonLabel_fr箫老、ButtonLabel_en_US、ButtonLabel_en黔州、ButtonLabel耍鬓,如果getBundle在列表中找不到匹配,會(huì)拋出MissingResourceException異常流妻,所以為了避免這個(gè)異常牲蜀,最好每次都使用沒有后綴的文件,在前面示例中就是ButtonLabel文件名绅这。

五涣达、格式化

上次已經(jīng)可以獲取到數(shù)據(jù)了,有些時(shí)候數(shù)據(jù)獲取到之后可以直接展示,但是如果涉及到時(shí)間度苔、數(shù)字匆篓、金額、動(dòng)態(tài)文本等數(shù)據(jù)時(shí)寇窑,又需要額外做下處理了鸦概,因?yàn)楸旧磉@些數(shù)據(jù)就是本地化敏感的,那么這個(gè)時(shí)候怎么辦呢甩骏?這時(shí)就需要對(duì)相應(yīng)的數(shù)據(jù)進(jìn)行格式化操作窗市。下面詳細(xì)做下說明。

5.1 數(shù)字與金額

數(shù)字與金額其實(shí)都是數(shù)值相關(guān)的處理饮笛,JDK提供了NumberFormat類進(jìn)行處理咨察,處理過程可以大致分為兩步:(1)getInstance方法得到實(shí)例;(2)format方法格式化數(shù)據(jù)福青。

比如long扎拣、long可以使用NumberFormat.getNumberInstance(Locale inLocale)方法獲得相應(yīng)本地化的對(duì)象實(shí)例,比如int可以使用getIntegerInstance(Locale inLocale)方法獲得對(duì)應(yīng)實(shí)例素跺,金額可以調(diào)用getCurrencyInstance(Locale inLocale)方法得到實(shí)例,還有百分比的情況可以調(diào)用getPercentInstance(Locale inLocale)得到實(shí)例誉券;最后再調(diào)用format方法即可指厌。

這里額外還說下DecimalFormat類,這個(gè)類主要做小數(shù)的格式化處理踊跟。比如有不少場景對(duì)于123456.789這樣的數(shù)字要格式化成123,456.789 踩验;這個(gè)時(shí)候DecimalFormat就非常實(shí)用。簡單示例如下:
<code>
NumberFormat nf = NumberFormat.getNumberInstance(locale);
DecimalFormat df = (DecimalFormat)nf;
df.applyPattern("###,###.###");
String output = df.format(value);
</code>

上面可以看到DecimalFormat格式化時(shí)會(huì)需要有個(gè)格式化的模式"###,###.###"商玫,而這個(gè)模式還可以支持更多靈活的語法箕憾。基本如下:

符號(hào) 含義
0 阿拉伯?dāng)?shù)字
# 阿拉伯?dāng)?shù)字,0如果無效的話就不顯示
. 小數(shù)的分隔符
, 分組的分隔符
E 分隔科學(xué)計(jì)數(shù)法中的尾數(shù)和指數(shù)
; 格式化分隔符拳昌,分隔正數(shù)和負(fù)數(shù)子模式
- 默認(rèn)的負(fù)數(shù)前綴
% 乘以100袭异,百分?jǐn)?shù)展示
? 乘以1000,千分?jǐn)?shù)展示
¤ 貨幣記號(hào)炬藤,由貨幣符號(hào)替換御铃。如果兩個(gè)同時(shí)出現(xiàn),則用國際貨幣符號(hào)替換沈矿。如果出現(xiàn)在某個(gè)模式中上真,則使用貨幣小數(shù)分隔符,而不使用小數(shù)分隔符
X 任意可以用在前綴或后綴的字符
' 用于在前綴或或后綴中為特殊字符加引號(hào)羹膳,例如 "'#'#" 將 123 格式化為 "#123"睡互;如果要?jiǎng)?chuàng)建單引號(hào)本身,就使用兩個(gè)單引號(hào)"# 9''123"

這里有兩個(gè)不太常用到的點(diǎn)做下說明:(1)格式里面有分號(hào)作分隔符,其實(shí)完整的模式應(yīng)該是subpattern;subpattern就珠,前一個(gè)subpattern是正數(shù)的格式化模式寇壳,后一個(gè)subpattern是負(fù)數(shù)的格式化模式,每一個(gè)subpattern的形式都可以用前面表格的去定義表示嗓违,不過負(fù)數(shù)的格式化模式是可選的九巡,通常情況下不會(huì)用;(2)前面表格的分隔符還可以定制化蹂季,使用DecimalFormatSymbols類就可以自定義分隔符冕广,具體使用時(shí)調(diào)用含DecimalFormatSymbols參數(shù)的DecimalFormat構(gòu)造方法,再進(jìn)行格式化處理即可偿洁。

5.2 日期與時(shí)間

日期與時(shí)間的處理撒汉,以前主要用到SimpleDateFormat這個(gè)實(shí)現(xiàn)類,JDK8新引進(jìn)了java.time包下的DateTimeFormatter類也可以進(jìn)行格式化處理涕滋。DateTimeFormatter可以看我前面寫的JDK8新特性一覽里面的介紹睬辐,下面以SimpleDateFormat舉例,:
<code>
SimpleDateFormat formatter = new SimpleDateFormat(pattern, currentLocale);
Date today = new Date();
String output = formatter.format(today);
System.out.println(pattern + " " + output);</code>

這里同樣有個(gè)格式化語法

符號(hào) 含義 類型 示例
G 紀(jì)元 Text AD
y 年份 Number 2009
M 月(在一年中的月分) Text & Number July & 07
d 日(在一個(gè)月中的天數(shù)) Number 10
h 小時(shí)(12小時(shí)制宾肺,1-12) Number 12
H 小時(shí)(24小時(shí)制溯饵,0-23) Number 0
m Number 30
s Number 55
S 毫秒 Number 978
E 日(在一周中的天數(shù)) Text Tuesday
D 日(在一年中的天數(shù)) Number 189
F 第幾周(這一天在這一個(gè)月的第幾周) Number 2 (2nd Wed in July)
w 第幾周(在一年的第幾周) Number 27
W 第幾周(這個(gè)月的第幾周) Number 2
a 上午/下午(am/pm) Text PM
k 小時(shí)(24小時(shí)制,1-24) Number 24
K 小時(shí)(12小時(shí)制锨用,0-11) Number 0
z 時(shí)區(qū) Text Pacific Standard Time
' 文本分隔(格式化內(nèi)容中插入文本時(shí)用到) Delimiter (none)
' 單引號(hào) Literal '

5.3 文本

在網(wǎng)站應(yīng)用里面丰刊,文本國際化應(yīng)該是最常用到的了。而且復(fù)雜情況下增拥,文本可能還是是固定不變的啄巧,可能是動(dòng)態(tài)數(shù)據(jù),還可能包含前面講的金額或時(shí)間等信息掌栅。比如文本是“我在xxx時(shí)間秩仆,在xxx網(wǎng)站,花費(fèi)了xxx錢猾封,購買了xxx東西”澄耍,這個(gè)時(shí)候時(shí)間、站名晌缘、金額逾苫、東西都不一樣。不過JDK的MessageFormat類提供了簡便的實(shí)現(xiàn)枚钓。

主要的步驟可以分為三步:(1)定義文本模板铅搓;(2)初始化MessageFormat類;(3)根據(jù)模板和動(dòng)態(tài)參數(shù)進(jìn)行格式化處理搀捷。下面是簡單示例:


定義模板.png

<pre><code>ResourceBundle messages = ResourceBundle.getBundle("i18n/Message",currentLocale);

Object[] messageArguments = {new Date(), messages.getString("goods"),"taobao",65.00};

MessageFormat formatter = new MessageFormat(messages.getString("template"),currentLocale );

String output = formatter.format(messageArguments);

System.out.println(output);</pre></code>

詳細(xì)代碼示例可以看我上傳的github項(xiàng)目代碼星掰。

通過上面的示例可以看到多望,MessageFormat類會(huì)自動(dòng)將傳為的參數(shù),按照ResourceBundle類獲取的模板要求做相應(yīng)的格式化處理氢烘,這樣就可以滿足動(dòng)態(tài)數(shù)據(jù)的展示了怀偷。上面在定義文本模板時(shí)用到了類似{3,number,currency}這樣的寫法,表示第三個(gè)參數(shù)格式類型為數(shù)字播玖,形式用金額形式椎工。這里也可以用{3}或者{3,nmuber}這樣就會(huì)相應(yīng)的默認(rèn)形式格式化。具體語法詳細(xì)講解鏈接

另外在有些語言環(huán)境下蜀踏,復(fù)數(shù)的表現(xiàn)形式不同维蒙,比如英語環(huán)境下,one file果覆、two files颅痊,這個(gè)時(shí)候的模板直接定義成{0}file這種形式就不太合適,這個(gè)時(shí)候就可以用到ChoiceFormat類進(jìn)行處理局待。

通過上面的三個(gè)步驟(本地化—數(shù)據(jù)獲取—格式化)斑响,整個(gè)國際化的過程就完成了。當(dāng)然簡單情況下本地化—數(shù)據(jù)獲取兩步也可能

最后還啰嗦一句钳榨,由于上面的每個(gè)點(diǎn)展開講都可以寫一篇甚至幾篇博文舰罚,限于篇幅,筆者主要把概念和常用部分重點(diǎn)做了強(qiáng)調(diào)薛耻,有了清晰的概念介紹與示例营罢,對(duì)于大家的理解應(yīng)該還是很有幫助的。不過這里還是強(qiáng)烈建議大家仔細(xì)閱讀下JAVA官方國際化教程昭卓,里面講解得非常詳細(xì),而且有更多示例瘟滨,筆者的一些示例也是在官方示例上面做的修改候醒。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市杂瘸,隨后出現(xiàn)的幾起案子倒淫,更是在濱河造成了極大的恐慌,老刑警劉巖败玉,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件敌土,死亡現(xiàn)場離奇詭異,居然都是意外死亡运翼,警方通過查閱死者的電腦和手機(jī)返干,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來血淌,“玉大人矩欠,你說我怎么就攤上這事财剖。” “怎么了癌淮?”我有些...
    開封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵躺坟,是天一觀的道長。 經(jīng)常有香客問我乳蓄,道長咪橙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任虚倒,我火速辦了婚禮美侦,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘裹刮。我一直安慰自己音榜,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開白布捧弃。 她就那樣靜靜地躺著赠叼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪违霞。 梳的紋絲不亂的頭發(fā)上嘴办,一...
    開封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音买鸽,去河邊找鬼涧郊。 笑死,一個(gè)胖子當(dāng)著我的面吹牛眼五,可吹牛的內(nèi)容都是我干的妆艘。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼看幼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼批旺!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起诵姜,我...
    開封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤汽煮,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后棚唆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體暇赤,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年宵凌,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了鞋囊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡瞎惫,死狀恐怖失暴,靈堂內(nèi)的尸體忽然破棺而出坯门,到底是詐尸還是另有隱情,我是刑警寧澤逗扒,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布古戴,位于F島的核電站,受9級(jí)特大地震影響矩肩,放射性物質(zhì)發(fā)生泄漏现恼。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一黍檩、第九天 我趴在偏房一處隱蔽的房頂上張望叉袍。 院中可真熱鬧,春花似錦刽酱、人聲如沸喳逛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽润文。三九已至,卻和暖如春殿怜,著一層夾襖步出監(jiān)牢的瞬間典蝌,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來泰國打工头谜, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留骏掀,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓柱告,卻偏偏與公主長得像截驮,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子际度,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

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