記一次大型React專案的國際化方案探索

Artyhacker發表於2019-03-08

背景

  • 專案:大型資料管理系統,涉及硬體裝置資料監控、日常業務資訊管理等
  • 技術:前後端分離,前端主要基於React-Redux
  • 需求:前端一鍵無縫切換多國語言

使用 react-intl

提起React專案國際化,首先想到著名的 react-intl 庫,這個庫提供了針對元件、日期、數字、字串等多種國際化方法。使用方法也很簡單:

  1. 將不同語言的翻譯檔案放在各自的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;
    複製程式碼
  2. 在入口檔案中配置 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"));
    複製程式碼
  3. 使用 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重連、資料丟失等一系列問題,由於不便於動用其他模組,思考過後剩下兩種方案:

  1. 語言切換時給予相應提示,然後跳轉到歡迎介面,這樣重新進入各子系統時會重新發起各種連線。由於並不會經常切換語言,而且語言切換一般也就是發生在剛進入系統的時候,所以這個方案是最實用也最省力的。又是萬惡的但是,由於專案背景比較複雜,上面領導的意思是像那些大型網站一樣“無縫”切換中英文,一跳轉就“有縫”了。。根本不考慮一個網站和一個大型B/S系統的差異,於是在需求降級可能性微乎其微的條件下,這個最合適的方案也只能作為緊急備用方案了。
  2. 修改 react-intl 庫,需要包裝庫中用到的每個方法,將資料來源由Context改為redux的store。做的時候發現基本只是在處理字串,就乾脆去掉了 react-intl 庫的依賴,手寫了個類似於intl庫中的 <FormattedMessage> 元件,使用的時候又發現只能用於元件的侷限性,又參考阿里的 react-intl-universal ,寫了個直接由key生成翻譯文字的方法。而這個方案目前還在完善和測試中。

相關文章