知乎專欄:zhuanlan.zhihu.com/c_215040065,個人部落格:blog.caichengnan.com/
前言
國際化是一個很常見的需求,之前沒有這方面的相關經驗,所以決定練一下手。正好最近在寫一個react骨架(新專案可直接移植的骨架),上網查了一下,常用的解決方案是yahoo的react-intl
庫,大致效果如下。
實現思路
-
首先解決靜態國際化,即根據瀏覽器的語言,自動載入對應的語言模板。這裡只需判斷
navigator.language
型別即可,然後通過react-intl提供了IntlProvider
元件,載入元件屬性的locale和messages,最後在需要用到國際化的元件裡,引入FormattedMessage
元件(react-intl內建),通過id對映到對應的國際化檔案裡的屬性(例如下面的en_US.js的hello)。即可實現靜態國際化。 -
動態國際化,即使用者可以通過按鈕切換,實現語言的切換。最容易想到的方案就是,在語言模板放在redux的store裡,提供一個切換語言的action,改變store裡的國家和語言模板,再觸發對應的
FormattedMessage
元件渲染。let's do it!
程式碼實現
- 在src下新建locale檔案存放國際化語言的檔案,這裡我們新建了en_US.js和zh_CN.js。
en_US.js
const en_US = {
hello: 'Hello, world!',
name: 'my name is {name}'
}
export default en_US;
複製程式碼
zh_CN.js
const zh_CN = {
hello: '你好,世界!',
name: '我的名字是 {name}'
}
export default zh_CN;
複製程式碼
一個是常規的變數hello
,一個是帶有變數{name}
的欄位name
。
- react-intl的
IntlProvider
元件類似redux的Provider
元件,需要在全域性引入。所以我們封裝一下Intl.jsx
元件,將redux和IntlProvider
相結合。
Intl.jsx
import React, { Component } from 'react';
import { addLocaleData, IntlProvider } from 'react-intl';
import { connect } from 'react-redux';
import zh_CN from './locale/lang/zh_CN';
import en_US from './locale/lang/en_US.js';
import zh from 'react-intl/locale-data/zh';
import en from 'react-intl/locale-data/en';
addLocaleData([...zh,...en]);
class Inter extends Component {
render() {
let { locale, localeMessage, children } = this.props;
return (
<IntlProvider key={locale} locale={locale} messages={localeMessage}>
{children}
</IntlProvider>
)
}
};
function chooseLocale(val) {
let _val = val || navigator.language.split('_')[0];
switch (_val) {
case 'en':
return en_US;
case 'zh':
return zh_CN;
default:
return en_US;
}
}
const mapStateToProps = (state, ownProps) => ({
locale: state.root.language,
localeMessage: chooseLocale(state.root.language)
});
let Intl = connect(mapStateToProps)(Inter);
export default Intl;
複製程式碼
解釋一下這個元件,元件是將redux裡的資料繫結到IntlProvider
元件上,addLocaleData
函式新增需要本地化的語言,這個需要宣告。redux中傳遞兩個props,locale
代表當前語言,localeMessage
代表locale裡的語言檔案內容。
這裡有一個很關鍵的地方,即key屬性。IntlProvider中的屬性變更並不會觸發FormattedMessage
重新渲染,剛開始想要forceUpdate強制更新元件,後來上網查了一個解決方案,在元件中加入key,就能解決這個問題
- 在實際使用語言的元件中引入
FormattedMessage
,當然react-intl還支援其他型別的轉換元件,比如時間型別FormattedDate
等等。可從官網上查詢API。github
App.js
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import { FormattedMessage } from 'react-intl';
import actions from '../actions/index.js';
import { connect } from 'react-redux';
class App extends Component {
changeLanguage() {
let lang = this.props.locale;
lang = lang === 'zh' ? 'en' : 'zh';
this.props.changeLanguage(lang);
}
render() {
const { locale } = this.props;
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">
<FormattedMessage
id="hello"
/>
</h1>
</header>
<p className="App-intro">
<FormattedMessage
id="name"
values={{ name: <b>{'carroll'}</b> }}
/>
</p>
<button onClick={() => this.changeLanguage()}>{locale === 'zh' ? '切換英文' : 'change chinese'}</button>
</div>
);
}
}
const mapStateToProps = (state, ownProps) => ({
locale: state.root.language,
});
const mapDispatchToProps = (dispatch, ownProps) => ({
changeLanguage: (val) => dispatch(actions.changeLanguage(val))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
複製程式碼
App.js主要實現了兩個功能,一個實現動態切換的action,一個FormattedMessage
id與資料的繫結。
- 最後在根檔案引入
Intl.jsx
即可
// ... 省略前面的引入
ReactDOM.render(
<Provider store={store}>
<Intl>
<App />
</Intl>
</Provider>,
document.getElementById('root'));
複製程式碼
- 下面是github上的原始碼
總結
整體實現下來,動態的國際化切換也沒有多難,但是我們要有思考。把國際化的資料放在redux中是否有些浪費,可否不引入FormattedMessage
也能解決文字的切換,在IntlProvider
上繫結key是否會造成其他無關元件的重新渲染。這些都是我們需要考慮的問題。
如果錯誤請指出,如果對您有幫助,麻煩點個贊