背景
- 專案:大型資料管理系統,涉及硬體裝置資料監控、日常業務資訊管理等
- 技術:前後端分離,前端主要基於React-Redux
- 需求:前端一鍵無縫切換多國語言
使用 react-intl
提起React專案國際化,首先想到著名的 react-intl 庫,這個庫提供了針對元件、日期、數字、字串等多種國際化方法。使用方法也很簡單:
-
將不同語言的翻譯檔案放在各自的js檔案中,同一處文字的多種語言翻譯使用相同的key
// en_US.js const en_US = { "intl_hello": "Hello!", } export default en_US; // zh_CN.js const zh_CN = { "intl_hello": "你好!", } export default zh_CN; 複製程式碼
-
在入口檔案中配置 react-intl 庫
// index.js import { addLocaleData, IntlProvider } from 'react-intl'; // 引入多語言環境 import en from 'react-intl/locale-data/en'; import zh from 'react-intl/locale-data/zh'; addLocaleData([...en, ...zh]); // 引入翻譯文字 import en_US from '.../intl/en_US.js'; import zh_CN from '.../intl/zh_CN.js'; const messagesMap = { en: en_US, zh: zh_CN } const locale = 'zh'; // 此處做了簡化,下文將從redux中獲取語言環境 render(( // 使用<IntlProvicer>包裝專案元件,配置語言環境和翻譯文字 <IntlProvider locale={local} messages={messages[local]}> //··· </IntlProvider> ), document.getElementById("root")); 複製程式碼
-
使用 react-intl 中內建的元件或方法替換需要做多語言的字串、時間等,具體可參考 API文件。
react-intl與redux
由於專案使用redux來管理狀態,將語言環境與翻譯文字都放入reducer中,使用相關action來觸發語言切換:
// actions.js
export const switchLocal = local => ({
type: 'SWITCH_INTL_LOCAL',
payload: { local },
});
// reducers.js
import en_US from '.../intl/en_US.js';
import zh_CN from '.../intl/zh_CN.js';
const messagesMap = {
en: en_US,
zh: zh_CN
}
const defaultLocal = { //預設語言環境,也可從瀏覽器或使用者配置資料中獲取
local: 'zh',
messages: messagesMap.zh
};
export const intlLocal = (state=defaultLocal, action) => {
switch(action.type) {
case 'SWITCH_INTL_LOCAL':
return {
local: action.payload.local,
messages: messagesMap[action.payload.local],
}
default:
return state;
}
}
// index.js
// 略去了檔案中的redux配置等程式碼
const { local, messages } = store.getState().intlLocal; // 從store中獲取語言配置
render((
// react-redux中的Provider需要包在IntlProvider之外,IntlProvider才能訪問到store
<Provider store={store}>
<IntlProvider locale={local} messages={messages}>
//···
</IntlProvider>
</Provider>
), document.getElementById("root"));
複製程式碼
完成之後發現初始化的時候可以訪問到store,使用指定的預設語言環境,但切換語言無效,排查後發現觸發action後reducer確實更改了,但沒有觸發元件更新。查閱相關文件後,使用react內部的key屬性來強制觸發更新:
// index.js
render((
<Provider store={store}>
// 加入key屬性來強制觸發更新
<IntlProvider key={local} locale={local} messages={messages}>
//···
</IntlProvider>
</Provider>
), document.getElementById("root"));
複製程式碼
存在問題與方案探討
在上一步中使用key來強制觸發更新,對於一般簡單的網站或前端系統來說,到這一步就可以了。
萬惡的但是,由於接手的系統過於複雜,使用key強制觸發元件更新時,會引起此<IntlProvider>
包裹下的所有元件全部被更新,導致類似於頁面整體被重新整理的效果,從而出現websocket重連、資料丟失等一系列問題,由於不便於動用其他模組,思考過後剩下兩種方案:
- 語言切換時給予相應提示,然後跳轉到歡迎介面,這樣重新進入各子系統時會重新發起各種連線。由於並不會經常切換語言,而且語言切換一般也就是發生在剛進入系統的時候,所以這個方案是最實用也最省力的。又是萬惡的但是,由於專案背景比較複雜,上面領導的意思是像那些大型網站一樣“無縫”切換中英文,一跳轉就“有縫”了。。根本不考慮一個網站和一個大型B/S系統的差異,於是在需求降級可能性微乎其微的條件下,這個最合適的方案也只能作為緊急備用方案了。
- 修改 react-intl 庫,需要包裝庫中用到的每個方法,將資料來源由Context改為redux的store。做的時候發現基本只是在處理字串,就乾脆去掉了 react-intl 庫的依賴,手寫了個類似於intl庫中的
<FormattedMessage>
元件,使用的時候又發現只能用於元件的侷限性,又參考阿里的 react-intl-universal ,寫了個直接由key生成翻譯文字的方法。而這個方案目前還在完善和測試中。