講到 i18n 真的就是只有機掰兩個字可以形容,因為它實在是一個非常破壞程式整體結構的東西,雖然可以搭配現有 Library 讓程式稍微容易維護一點,但是,如何在網站 Scale Up 之後 Lazy Load 語言包?如何讓程式在支援 i18n 的情況下依然高效運作?如何在 Library 限制的架構下盡可能讓整個網站都支援 i18n?
根據我的經驗,最好打從一開始就讓整個 Web 支援 i18n,如果一開始直接寫死語系,後面想要轉為多國語系時幾乎是整個專案都要砍掉重新規劃架構,所以我們的 Boilerplate 也是直接內建部分 i18n,選用的 Library 是 React-Intl。
React-Intl 是 Yahoo 公司推出的 React 多國語系 Library,裡面有非常多的 i18n 外加 l10n 的功能,同時它也是 React 社群中最熱門的 i18n 模組。
各位可以回顧一下 Day 14 - Infrastructure - Isomorphic Routing 曾經提過在 Root Component 用到了 LocaleProvider
元件,這是我們在 Boilerplate 中包裝 React-Intl 的 IntlProvider
所使用的元件,這麼做的目的有兩個:
當 Client Side 頁面初始化或是 Server Side 收到 Request 時,我們透過 Dispatch Action 把 Locale 存入 Store 裡,也就是說 IntlProvider
的 props.locale
需要依賴 Redux,所以我們的 Boilerplate 額外寫了 LocaleProvider
來注入 Store 中的 locale。
另一方面我們也考慮到大家的 i18n 不一定會採用 React-Intl 來實作,所以我把替換 Library 的彈性保留在 LocaleProvider
中,才不至於需要同時更改 Client Side 與 Server Side 的 Root Component。大家可以參考底下 Client Side 與 Server Side 的寫法,是共用 LocaleProvider
的。
render(
<Provider store={store}>
<LocaleProvider>
<Router
history={history}
onUpdate={logPageView}
{...renderProps}
>
{routes}
</Router>
</LocaleProvider>
</Provider>
, document.getElementById('root'));
完整程式碼:src/client/index.js
const markup = '<!doctype html>\n' + renderToString(
<Html
initialState={finalState}
assets={__webpackIsomorphicTools__.assets()}
>
<Provider store={req.store}>
<LocaleProvider>
<RouterContext {...renderProps} />
</LocaleProvider>
</Provider>
</Html>
);
在我們的 Boilerplate 裡其實沒有使用到太多 React-Intl 的功能,多數情況下只有用到 FormattedMessage
元件,我們另外使用自訂的 Text
元件來包裝它,這同樣也是為了日後彈性考量,例如當你想要更換 i18n Library 或是甚至直接摘掉 Library 自己刻的話。不必把每個使用到 React-Intl 或 FormattedMessage 的元件都翻修過一次,只需要修改 Text 元件即可。