[ 一起學React系列 -- 10 ] i18n

今天來介紹一個非常international的東西。

i18n
國際化(internationalization)的簡稱。之所以叫i18n,是因爲字母i和n之間有18個字母,所以才叫i18n。不要認爲這是一個高大上的名詞,其實就是因爲懶才簡寫的。hiahiahia...

因爲本系列是以React爲中心,所以只介紹React項目中的國際化解決方案。當然還有很多很多...很多別的國際化解決方案,但是不是所有的輪子都適合React這輛開往幼兒園的車。
實際上國際化在日常項目中用的沒那麼頻繁,除非有業務需求,比如要做一個非常international的項目。目前在React中比較熱門的兩個包就是react-intl-universalreact-intl。因爲本文重點介紹對象是前者,所以我們先簡單介紹下後者。當然在這裏不會把它的使用方法列出來,而是把它的缺點列出來,爲什麼呢?因爲筆者懶啊!
react-intl不足的地方主要是兩個:

它只能用於視圖層。舉個例子,比如React.Comoponent對象,但是對於Vanilla JS就會顯得很無力了,因爲它無法在Vanilla JS中實例化。(這裏會有人感到奇怪,Vanilla JS是什麼鬼?哈哈...百gu度ge吧,不會發現新大陸)!

其次

想使用國際化方法,我們必須要利用它的一個方法將自己的組件轉化成另外一個class。這就比較蛋疼了,例子如下:
import { injectIntl } from 'react-intl';
class MyComponent extends Component {
  render() {
    const intl = this.props;
    const title = intl.formatMessage({ id: 'title' });
    return (<div>{title}</div>);
  }
};
export default injectIntl(MyComponent);

看我筆者第一篇文章的朋友應該有印象:所有被包裹過的組件,如果你想獲得原本的組件的對象,那得調用相應的方法。這裏也不例外,如果我們想獲取組件的原對象,那就得這麼做:

class MyComponent {...}
export default injectIntl(MyComponent, {withRef: true});
 
class App {
  render() {
    <MyComponent ref="my"/>
  }
  getMyInstance() {
    console.log('getMyInstance', this.refs.my.getWrappedInstance());
  }
}

這樣寫會不會覺得太麻煩了...
所以Alibaba前端組就按捺不住了,然後就搞出了自己的react-intl-universal。看名字不就是在react-intl後面加個universal嗎?的確是這樣,不過筆者不清楚這個框架的核心邏輯是不是參考的react-intl,但是單從名字來看就有點"可疑"了,翻譯就是react-intl的通用版(當然,純屬意淫,一笑而過!)。

react-intl-universal

作爲一個國際化解決方案,首先實現國際化是它的基本功能。其次它還有一些別的功能,比如文本格式化、貨幣格式化、時間格式化等等,我相信這些都是我們頁面開發經常使用到的功能。

i18n

首先來看一下它的技術功能:國際化
react-intl-universal採用了與組件無關的方法來實現國際化。國際化的本質其實就是將我們預先設置好的不同語言的句子按照語言環境顯示在頁面上。

import intl from 'react-intl-universal';

通過intl這個對象來實現初始化和國際化處理。我們可以認爲這個intl是一個單例對象。我們在App啓動的時候對其進行初始化,爾後在別的地方再次導入的時候仍然是一個已經初始化過的對象。在這種情況下,國際化處理就會變得異常簡單。其次就是準備多語言句子了,傳統的在前端處理這個問題是將不同語言的句子放在不同的json文件中再導出,文件結構如下:
clipboard.png
這樣我們就可以在App啓動或者切換語言的時候導入相應的json對象了。

首先是API介紹

intl對象主要有三個常用的用於國際化處理的API,determineLocale、init、get

  • determineLocale
    看到方法名就應該知道它是用來幹什麼了。它用來確定在整個體系中使用的是哪種語言。看代碼:
let currentLocale = intl.determineLocale({
    urlLocaleKey: "lang",
    cookieLocaleKey: "lang"
});

react-intl-universal確定語言的方式有三種,一個是通過urlLocaleKey,即lang關鍵字從url中獲取是哪種語言。比如:http://localhost?lang=en-US,因爲lang對應的值是en_US,所以語言爲英文。其次是從Cookie獲取,因爲Cookie也是以鍵值對形式存儲的,所以會檢查當前域下的Cookie是否有對應的lang。如果上述兩種都沒有,那麼會默認使用瀏覽器當前的語言類型。當然上述的urlLocaleKey和cookieLocaleKey是可以自定義的,不是固定的lang.

  • init

init方法即用來初始化intl對象。初始化參數主要是兩個,一個是currentLocale即當前的語言,另一個是locales即當前語言對應的json對象,比如{"en-US":{"key1":"value1"} 或者 {"zh-CN":{"key1":"值1"}}

  • get
    get方法就相對簡單,就是根據鍵去intl中獲取對應的值,這裏不做過多解釋。

完整的初始化過程如下:

class App extends Component{
    ....
    
    componentDidMount() {
        this.loadLocales();
    }

    loadLocales() {
        const _self = this;
        let currentLocale = intl.determineLocale({  //如果cookie和url中均沒有相關參數,那麼以瀏覽器語言爲準
            urlLocaleKey: "lang",
            cookieLocaleKey: "lang"
        });

        http
            .get(`locales/${currentLocale}.json`)   //理解爲按需加載並且locales文件夾需要放在public文件下供http訪問
            .then(res => {
                return intl.init({
                    currentLocale,
                    locales: {
                        [currentLocale]: res.data //如果key是變量,那麼需要用[]包一下
                    }
                });
            })
            .then(() => {
                _self.setState({initDone: true});
            });
    }
    
    ....
}

然後在需要國際化的地方這麼使用

import intl from 'react-intl-universal';
<p>{intl.get('name')}</p>

是不是很簡單? 而且完全避免了react-intl的兩個缺點。

格式化工具

前面說了react-intl-universal不僅僅可以用來做國際化處理,還可以用來做簡單的文本格式化處理。下面我們列舉幾個常用的。

Html Snippet

假如我們的json文件中有這麼一段

...
"red": "<p style='color:red'>紅色</p>",
...

如果我們直接用get方法獲取的話,那麼會直接把<p style='color:red'>紅色</p>給打印出來。如果我們想將它以html片段的形式打印出來的話,就使用getHTML方法,它在獲取到句子的時候會進行解析並生成最終的Html Snippet。

Default Message

缺省值其實就是默認值,是對於json鍵值對的默認值。將入我們去獲取一個json中沒有的鍵值對那麼系統就會報錯。如何去規避這個問題呢?react-intl-universal給我們提供了這樣一個方法:

intl.get('not-exist-key').defaultMessage('default message')

這是一個鏈式調用。如果json中沒有not-exist-key這個鍵,那就會默認返回defaultMessage的參數。簡寫是intl.get('not-exist-key').d('default message')

Message With Variables

假如某個句子包含了一個變量怎麼辦?比如一個用戶名,我們只有在用戶登錄的時候才知道他的用戶名。

{
    "me": "你好,我是{me}"
}

此時就用到了get放的第二個參數。對於上面的例子,我們可以這樣處理:

<p>{intl.get('me', {'me': '皮卡丘'})}</p>

get方法會找出句子中被{}包住的變量me,然後在第二個參數(json對象)找出me對應的值皮卡丘並將{me}整個用皮卡丘替換。另外需要注意的是,json對象只能爲一層,不可嵌套

Display Currency

它還可以用來格式化貨幣。假如有這麼一段句子

{
  "price": "這件衣服是 {price,number,CNY} 人民幣",
}

如果我們想將一個數字以人民幣的形式寫進去的話可以這麼做:

{intl.get('price', {'price': 1000})}

最終顯示結果是:這件衣服是 ¥1,000 人民幣
其實它做了兩件事:一個是加符號,另一個是加分隔符。同時CNY表示人民幣,USD表示美元

Display Dates

然後是日期的處理。假如有這麼一段話:

{
    "date": "今天是{date,date,full}"
}

然後我們這麼使用它的話:

<p>{intl.get('date',{'date':new Date()})}</p>

顯示結果是今天是2018年12月3日星期一。其實{date,date,full}這段指令就是將date變量替換成對應日期(new Date())並以long形式展示。

同時日期展示形式有四種

  • short: shows date as shortest as possible
  • medium: shows short textual representation of the month
  • long: shows long textual representation of the month
  • full: shows dates with the most detail

他們之間有什麼不同呢?我們用剛剛的例子做個展示:

  • short: 今天是18/12/3
  • medium: 今天是2018年12月3日
  • long: 今天是2018年12月3日
  • full: 今天是2018年12月3日星期一

Display Times

最後是時間。我們按部就班來。假如有這麼一段話:

{
    "time": "現在時間是{time,time,short}"
}

然後我們這麼使用它的話:

 <p>{intl.get('time',{'time':new Date()})}</p>

顯示結果是現在時間是下午5:54。其實{time,time,short}這段指令就是將time變量替換成對應日期(new Date())並以short形式展示。

但是時間展示形式只有三種,它沒有full

  • short: shows date as shortest as possible
  • medium: shows short textual representation of the month
  • long: shows long textual representation of the month

他們之間有什麼不同呢?我們用剛剛的例子做個展示:

  • short: 現在時間是下午5:54
  • medium: 現在時間是下午5:58:22
  • long: 現在時間是GMT+8 下午5:58:50

上述貼出來的示例都是在中文環境下。如果有興趣的朋友可以把整個例子download下來本地運行下,邊看邊寫,受益匪淺。好了,收拾下班咯...

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章