1. 程式人生 > 其它 >React專案中實現多語言支援

React專案中實現多語言支援

歡迎關注前端早茶,與廣東靚仔攜手共同進階

前端早茶專注前端,一起結伴同行,緊跟業界發展步伐~

公眾號作者:廣東靚仔

前言

語言國際化,也有人說成是語言本地化,其實就是為Web App新增多語言,我們的專案當前包含了中文版和英文版,按理來說『逐字替換』也不是多大事兒,但是,這麼Low的做法,有錢途嗎?

一開始的時候,我考慮的是傳統的為整個專案新增config檔案,根據不同的語言和地區,載入不同的config檔案,就能夠達到介面語言切換的目的。當然,也正是因為這個想法太過於幼稚,所以才被稱為『一開始』的想法。語言的國際化不僅僅是將介面上的UI文字翻譯成另一種語言,還包括了日期&時間顯示,數字顯示(英文環境下每隔3位一個逗號:1,000),量詞的顯示(一個蘋果是apple,兩個蘋果就應該是apples),甚至還有一個字串中間插了一個變數的情況("今天午飯吃了{count}個雞腿")...

所以這並不只是一個簡單的字元替換問題,並且,我們要很方便的匯出一個目錄,放到word或者page當中,給到其他同事對照著進行翻譯工作,這個非常重要!!難道你要讓產品經理直接在程式碼裡改麼?或者你想一個一個搜尋替換?不考慮清楚就乾的話,相信我,You'll pay for this。

作為一個有追求的程式碼家,你肯定不希望在index.html當中增加一行<Script>引用吧?另外,UI中的文字全部都是使用圖片的那個同學,請起立,滾。如果想要在一個React專案中,優雅的import something from somewhere,然後將介面中的文字用<首字母大寫 /> 元件替代,最後通過簡單的配置實現語言的國際化,那我們就用React-intl吧。

注意:本文說的是用法,原始碼我也沒有拜讀過,太深的東西去github給作者留言吧。

React-intl

專案地址:https://github.com/yahoo/reac...

React-intl是雅虎的語言國際化開源專案FormatJS的一部分,通過其提供的元件和API可以與ReactJS繫結。上面這句話援引了官方文件的說辭,主要表達的是,這是一個很屌的開源專案,有大團隊支援,使用量也很大,不會太坑爹,你們放心用。雖然雅虎都快被收購了。

React-intl提供了兩種使用方法,一種是引用React組建,另一種是直接調取API,官方更加推薦在React專案中使用前者,只有在無法使用React元件的地方,才應該呼叫框架提供的API,事實上,我在專案的過程中真的遇到了無法使用元件的情況,這個我會另外寫一篇文章來描述。

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提供的所有元件介紹完了,下面就給大家介紹一下具體如何去使用吧。

React-intl 使用步驟

(本文例子執行在OSX環境,Window操作方法的終端在安裝的時候要注意用管理員身份執行)

1. 安裝React-intl

假設你已經在你的系統中安裝了node.js和npm,如果你還不知道這兩個是什麼東西,請自行百度,對,在百度都能找到答案。

開啟終端,進入專案根目錄,輸入以下指令安裝React-intl:

npm install react-intl -save

注意:為了相容Safari各個版本,需要同時安裝 intl,intl在大部分的『現代』瀏覽器中是預設自帶的,但是Safari和IE11以下的版本就沒有了,這裡需要留個心眼。

安裝intl需要在終端中輸入以下指令:

npm install intl --save

這裡還有一個注意:由於React-intl的每一個元件的使用方法大同小異,和ReactJS的語法完全一致,所以我就僅僅描述如何使用<FormattedMessage />這個元件的用法,藉此拋磚引玉,相信看完之後已經足夠幫助你迅速的去使用這個開源框架了。

2. 引用React-intl

import { FormattedMessage } from 'react-intl'; 

由於我使用的是ES6 的語法,所以是支援直接引用元件的。你當然可以使用ES5的方式引用,但是,這樣有前途麼?

require ReactIntl from 'react-intl';

3. 建立locale配置檔案

這裡,我們將檔案命名為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 }是什麼鬼?其實悟性高一些的話就應該已經猜到,這個應該就是前面提到過的在字串中插入動態引數的用法,事實上也是這樣的。

4. 使用<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 />,這個功能的實際意義就是可以在英文網站中巢狀一箇中文的或者德語的或者法語的板塊,應用起來會更加靈活一些。

5. 使用<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.

歡迎關注前端早茶,與廣東靚仔攜手共同進階

前端早茶專注前端,一起結伴同行,緊跟業界發展步伐~

公眾號作者:廣東靚仔