最近在項目中添加了語言國際化的功能。html
語言國際化,也有人說成是語言本地化,其實就是爲Web App添加多語言,咱們的項目當前包含了中文版和英文版,按理來講『逐字替換』也不是多大事兒,可是,這麼Low的作法,有錢途嗎?node
一開始的時候,我考慮的是傳統的爲整個項目添加config文件,根據不一樣的語言和地區,加載不一樣的config文件,就可以達到界面語言切換的目的。固然,也正是由於這個想法太過於幼稚,因此才被稱爲『一開始』的想法。語言的國際化不只僅是將界面上的UI文字翻譯成另外一種語言,還包括了日期&時間顯示,數字顯示(英文環境下每隔3位一個逗號:1,000),量詞的顯示(一個蘋果是apple,兩個蘋果就應該是apples),甚至還有一個字符串中間插了一個變量的狀況("今天午餐吃了{count}個雞腿")...react
因此這並不僅是一個簡單的字符替換問題,而且,咱們要很方便的導出一個目錄,放到word或者page當中,給到其餘同事對照着進行翻譯工做,這個很是重要!!難道你要讓產品經理直接在代碼裏改麼?或者你想一個一個搜索替換?不考慮清楚就乾的話,相信我,You'll pay for this。git
做爲一個有追求的代碼家,你確定不但願在index.html當中增長一行<Script>引用吧?另外,UI中的文字所有都是使用圖片的那個同窗,請起立,滾。若是想要在一個React項目中,優雅的import something from somewhere,而後將界面中的文字用<首字母大寫 /> 組件替代,最後經過簡單的配置實現語言的國際化,那咱們就用React-intl吧。github
注意:本文說的是用法,源碼我也沒有拜讀過,太深的東西去github給做者留言吧。web
項目地址: https://github.com/yahoo/reac...
React-intl是雅虎的語言國際化開源項目FormatJS的一部分,經過其提供的組件和API能夠與ReactJS綁定。上面這句話援引了官方文檔的說辭,主要表達的是,這是一個很屌的開源項目,有大團隊支持,使用量也很大,不會太坑爹,大家放心用。雖然雅虎都快被收購了。npm
React-intl提供了兩種使用方法,一種是引用React組建,另外一種是直接調取API,官方更加推薦在React項目中使用前者,只有在沒法使用React組件的地方,才應該調用框架提供的API,事實上,我在項目的過程當中真的遇到了沒法使用組件的狀況,這個我會另外寫一篇文章來描述。redux
React-intl提供的React組件有以下幾種:瀏覽器
<IntlProvider />
包裹在須要語言國際化的組建的最外層,爲包含在其中的全部組建提供包含id和字符串的鍵值對。(如:"homepage.title":"Hommily";
)微信
a. <FormattedDate />
用於格式化日期,可以將一個時間戳格式化成不一樣語言中的日期格式。
傳入時間戳做爲參數:
<FormattedDate value={new Date(1459832991883)} />
輸出結果:
<span>4/5/2016</span>
b. <FormattedTime>
用於格式化時間,效果與<FormattedDate />類似。
傳入時間戳做爲參數:
<FormattedTime value={new Date(1459832991883)} />
輸出結果:
<span>1:09 AM</span>
c. <FormattedRelative />
經過這個組件能夠顯示傳入組件的某個時間戳和當前時間的關係,好比 「 10 minutes ago"。
傳入時間戳做爲參數:
<FormattedRelative value={Date.now()} />
輸出結果:
<span>now</span>
10秒以後的輸出結果:
<span>10 seconds ago</span>
1分鐘以後的輸出結果:
<span>1 minute ago</span>
a. <FormattedNumber />
這個組件最主要的用途是用來給一串數字標逗號,好比10000這個數字,在中文的語言環境中應該是1,0000,是每隔4位加一個逗號,而在英語的環境中是10,000,每隔3位加一個逗號。
傳入數字做爲參數:
<FormattedNumber value={1000} />
輸出結果:
<span>1,000</span>
b. <FormattedPlural />
這個組件可用於格式化量詞,在中文的語境中,其實不太會用獲得,好比咱們說一個雞腿,那麼量詞就是‘個’,咱們說兩個雞腿,量詞仍是‘個’,不會發生變化。可是在英文的語言環境中,描述一個蘋果的時候,量詞是apple,當蘋果數量爲兩個時,就會變成apples,這個組件的做用就在於此。
傳入組件的參數中,value爲數量,其餘的爲不一樣數量時對應的量詞,在下面的例子中,一個的時候量詞爲message,兩個的時候量詞爲messages。實際上能夠傳入組件的量詞包括 zero, one, two, few, many, other 已經涵蓋了全部的狀況。
<FormattedPlural value={10} one='message' other='messages'/>
傳入組件的量詞參數能夠是一個字符串,也能夠是一個組件,咱們能夠選擇傳入<FormattedMessage />
組件,就能夠實現量詞的不一樣語言的切換。
輸出結果:
<span>messages</span>
a. <FormattedMessage />
這個組件用於格式化字符串,是全部的組件中使用頻率最高的組件,由於基本上,UI上面的每個字符串都應該用這個組件替代。這個組件的功能豐富,除了能夠根據配置輸出不一樣語言的簡單字符串以外,還能夠輸出包含動態變化的參數的複雜字符串,具體的用法在後面的例子中會慢慢敘述。
好比咱們在locale配置文件中寫了以下內容:
const app = { greeting:'Hello Howard!", } export default app;
使用這個組件的時候,咱們這麼寫:
<FormattedMessage id='app.greeting' description='say hello to Howard' defaultMessage='Hello, Howard!' />
id指代的是這個字符串在locale配置文件中的屬性名,description指的是對於這個位置替代的字符串的描述,便於維護代碼,不寫的話也不會影響輸出的結果,當在locale配置文件中沒有找到這個id的時候,輸出的結果就是defaultMessage的值。
輸出的結果:
<span>Hello, Howard!</span>
b. <FormattedHTMLMessage />
這個組件的用法和<FormattedMessage />徹底相同,惟一的不一樣就是輸出的字符串能夠包含HTML標籤,可是官方不太推薦使用這個方法,若是能夠想辦法用<FormattedMessage />的話,就不該該使用這個組件,我揣測應該是性能方面不如<FormattedMessage />,這個組件的用法我就不舉例了。
Well,到此爲止,已經把React-intl提供的全部組件介紹完了,下面就給你們介紹一下具體如何去使用吧。
(本文例子運行在OSX環境,Window操做方法的終端在安裝的時候要注意用管理員身份運行)
假設你已經在你的系統中安裝了node.js和npm,若是你還不知道這兩個是什麼東西,請自行百度,對,在百度都能找到答案。
打開終端,進入項目根目錄,輸入如下指令安裝React-intl:
npm install react-intl -save
注意:爲了兼容Safari各個版本,須要同時安裝 intl,intl在大部分的『現代』瀏覽器中是默認自帶的,可是Safari和IE11如下的版本就沒有了,這裏須要留個心眼。
安裝intl須要在終端中輸入如下指令:
npm install intl --save
這裏還有一個注意:因爲React-intl的每個組件的使用方法大同小異,和ReactJS的語法徹底一致,因此我就僅僅描述如何使用<FormattedMessage />這個組件的用法,藉此拋磚引玉,相信看完以後已經足夠幫助你迅速的去使用這個開源框架了。
import { FormattedMessage } from 'react-intl';
因爲我使用的是ES6 的語法,因此是支持直接引用組件的。你固然可使用ES5的方式引用,可是,這樣有前途麼?
require ReactIntl from 'react-intl';
這裏,咱們將文件命名爲zh_CN.js和en_US.js,表明中文和美式英語的配置包。
在zh_CN.js編寫以下代碼:
const zh_CN = { hello:"你好,方浩!", superHello:"你好,{ someone } !" } export default zh_CN;
在en_US.js編寫以下代碼:
const en_US = { hello:"Hello, Howard!", superHello:"Hello, { someone } !" } export default en_US;
因而,咱們就建立好了locale文件,可是,在實際的項目中配置文件不會這麼簡單,您可能須要根據業務需求按照不一樣的頁面或者不一樣的功能塊建立不一樣的文件樹,而後用模塊化的方法將不一樣的配置文件進行組織,已達成你的目標,這裏我也就沒能力逼逼太多了。
你可能想問,{ someone }
是什麼鬼?其實悟性高一些的話就應該已經猜到,這個應該就是前面提到過的在字符串中插入動態參數的用法,事實上也是這樣的。
<IntlProvider />
使用<IntlProvider />
組件包裹住須要您須要進行語言國際化的組件,用法和React-redux的<Provider />
差很少,當<IntlProvider />
包裹住某個組件的時候,這個組件自己和組件內部包含的子組件就能夠得到全部React-intl提供的接口以及在<IntlProvider />
中引入的locale配置文件的內容。
import React from 'react'; import { render } from 'react-dom'; //引入locale配置文件,具體路徑根據實際狀況填寫 import zh_CN from './zh_CN'; import en-US from './en-US'; //若是瀏覽器沒有自帶intl,則須要在使用npm安裝intl以後添加以下代碼 import intl from 'intl'; addLocaleDate([...en,...zh]); ... ... render( <IntlProvider locale={'en'} messages={en_US} > <App /> </IntlProvider>, document.getElementById('container') );
<IntlProvider />
須要傳遞兩個參數:
locale是傳遞須要國際化的語言的縮寫,經過這個參數能夠肯定格式化日期,數字,量詞的時候按照哪種語言的規則,這個是規則是intl提供的,通常瀏覽器會內置這個庫,可是在Safari和IE11以前須要本身安裝,安裝的方法前面已經說起,請本身翻閱。
messages是用於傳遞剛剛咱們在第3步中定義的配置文件的,從示例代碼中咱們能夠看出,首先咱們使用Import語句引入了配置文件,而後將配置文件的內容傳遞給了messages這個參數,此時<App />組件中的全部組件均可以拿到配置文件中的內容了。
那個跳起來的同窗,請先坐下,我猜你是想問,是否是每次都要手動修改這兩個參數以適配不一樣語言呢?
其實否則,咱們徹底能夠按照下面的作法自動識別當前瀏覽器的語言:
chooseLocale(){ switch(navigator.language.split('_')[0]){ case 'en': return 'en_US'; break; case 'zh': return 'zh_CN'; break; ... ... ... default: return 'en_US'; break; } } render( <IntlProvider locale={navigator.language} messages={chooseLocale()} > <App /> </IntlProvider>, document.getElementById('container') );
您還須要知道的是,<IntlProvider />
是能夠嵌套使用的,也就是說,在一個<IntlProvider />
內部還能夠有N個<IntlProvider />
,這個功能的實際意義就是能夠在英文網站中嵌套一箇中文的或者德語的或者法語的板塊,應用起來會更加靈活一些。
<FormattedMessage />
前面的幾個步驟其實都是爲了這個步驟作鋪墊的,在添加了<IntlProvoder />以後,咱們就能夠在其包裹的<App />及<App />包含的全部組件中獲取到配置文件的信息,傳入<FormattedMessage />組件的id參數也能其在配置文件中對應的字符串了。
使用的方法以下:
<FormattedMessage id='hello' description='say hello to Howard.' defaultMessage='Hello, Howard' />
在Js執行的時候,組件就會找到配置文件中,‘hello'鍵名對應的字符串'Hello, Howard!'.
輸出的結果爲:
<span>Hello, Howard!</span>
那麼如何輸出含有動態參數的字符串呢?好比Hello,Johnson!,若是我要問候的對象是一個變量呢?
那就這麼寫唄。。
<FormattedMessage id='superHello' description='say hello to Howard.' defaultMessage='Hello, {someone}' values={ someone:this.props.name, } />
以上的例子中,賦給someone的就是一個變量(假設這個變量是經過參數傳進這個組件的),注意,若是是這樣的話,那麼locale配置文件中就要這麼寫。
superHello:"你好,{ someone } !"
前面其實提過了,怕你忘了...我已經悄無聲息的把id換成了superHello。
更牛逼的是,這個someone還能夠包含HTML標籤!
<FormattedMessage id='superHello' description='say hello to Howard.' defaultMessage='Hello, {someone}' values={ someone:<b>this.props.name</b>, } />
輸出結果:
<span>Hello, <b>Howard</b>!</span>
因而,這個名字就被加粗了。
眼尖的同窗又要跳起來了,「webFunc,爲何全部的輸出都帶一個<span>標籤,我就不能換成別的麼?」
不要着急,我正要說這個,對於這個問題,官方的文檔是這麼說的。
By default<formattedMessage>
will render the formatted string into
a<span>
. If you need to customize rendering, you can either wrap it
with another React element (recommended), specify a different tagName
(e.g., 'div'), or pass a function as the child.
翻譯過來就是,默認的是會包裹在<span>
標籤中的,若是想要讓輸出的字符串包裹在其餘標籤中的話,好比你想包裹在<div>
中,你就把<FormattedMessage />
組件包含在一對<div>
中間,這是一種官方更加推薦的作法。
<div> <FormattedMessage id='hello' description='say hello to Howard.' defaultMessage='Hello, Howard!" /> </div>
Well, that's stupid...
或者你能夠給<FormattedMessage>傳入一個tagName的參數。好比:
<FormattedMessage id='hello' tagName = 'div' description='say hello to Howard.' defaultMessage='Hello, Howard!' />
就會輸出:
<div>Hello, Howard!</div>
比較奇葩的是,也是我揣測做者不推薦使用這種方法的緣由是...只要你高興,tagName能夠傳入任意字符串,好比 shit:
<FormattedMessage id='hello' tagName = 'shit' description='say hello to Howard.' defaultMessage='Hello, Howard!' />
就會輸出:
<shit>Hello, Howard!</shit>
Yes, shit happens.
看到這裏,你應該已經會使用React-intl對你的項目進行語言國際化了,沒有進一步描述的地方,請自行查閱官方文檔(項目地址:https://github.com/yahoo/reac...,或者給我留言,雖然我不必定會及時回覆。
--寫在後面:
語言國際化應該是一個比較常常遇到的需求,可是我在完成項目的過程當中,看到的中文的資料卻至關少,雖然這不是一篇很是牛叉的技術文章,可是可能會幫到不少人,如若如此,也便知足了。
——方浩(webFunc)
對了,你能夠關注一下個人微信公衆號:webcoding