隨著國際化發展,多語言的需求越來越常見,單一的語言已經遠不能滿足需求了。作為一個組件庫,支持多語言也是基本能力。 多語言功能的本質其實是文本的替換,一個詞匯“OK”,在英文語境下是“OK”,日語語境下是“確認”,中文語境下可能是“確定”也可能是“確認”“好的”等等。
本文將以簡單組件為切入點,向大家展示Fusion組件庫是如何支持多語言能力的。
單組件的多語言
我們以一個常見的組件Search舉例,用戶輸入內容后可通過點擊“搜索”、“清除”按鈕觸發相應的事件,簡化代碼后如下:
class Search extends React.Component {render() {return (<div><input /><button>搜索</button><button>清除</button></div>);}
}export default Search;
復制代碼
多語言處理最簡單直接的辦法是直接替換文本,不同語言環境下可能要將“搜索”替換為“search”、“サーチ”,將“清除”替換為“clear”、"クリア"等。同時作為一個組件庫,涉及到的大多是簡單詞匯而不是句子,因此我們首選配置化的方式:
// 抽取語言包
// search-en-us.js
{search: 'search',clear: 'clear'
}
// search-zh-cn.js
{search: '搜索',clear: '清除'
}
復制代碼
import searchZhCN from 'search-zh-cn';class Search extends React.Component {static propTypes = {locale: PropTypes.object};static defaultProps = {locale: searchZhCN};render() {return (<div><input /><button>{locale.search}</button><button>{locale.clear}</button></div>);}
}export default Search;
復制代碼
這樣就實現了單個組件Search的多語言支持。
但是,為每個組件對應一個語言包文件的做法顯然很不方便。Fusion Next作為一個PC端的React組件庫有50+組件,內置詞匯70多條,目前有13個組件需要國際化語言能力。
以語種為單位,將同一種語言下的映射關系放到一個文件里進行處理的方式更為高效。
多組件的多語言
為便于維護管理,增強可拓展性,我們以語種為單位抽取語言包。將同一語種下所有組件的語言對象{key: '文案'}
放到一起,以displayName作為key,語言對象作為value,調整語言包如下:
// 抽取語言包
// zh-cn.js
{Search: {search: '搜索',clear: '清除'},Dialog: {},...
}
復制代碼
這也是Fusion現在語言包的結構 unpkg.com/@alifd/next… 由于語言包結構的調整,需要同時修改Search組件語言對象的默認值:
import zhCN from 'zh-cn';class Search extends React.Component {...static defaultProps = {locale: zhCN.Search}...
}export default Search;
復制代碼
在使用時,用戶將語言包對象以props參數的形式傳給組件即可直接改變文案:
import jaJP from 'xxxx/ja-jp.js';<Search locale={jaJP.Search}>
<Dialog locale={jaJP.Dialog}>
復制代碼
然而,在web應用越來越復雜的現在,很多項目里里可能會用到幾十甚至上百個組件,這樣給每個組件手動傳locale參數的方式一方面比較蠢,另一方面開發者需要關心locale參數,在語言切換時改變值的內容。
并且語言的設置大都是以項目(或者頁面)為單位的,有沒有更聰明、對開發者更友好的使用方式呢?
一鍵設置語言
import zhCN from 'zh-cn';<ConfigProvider locale={zhCN}><Search /><Dialog />
</ConfigProvider>
復制代碼
使用者在使用時基礎組件時不用關心locale的變化,子組件們共享了<ConfigProvider>
組件上傳入的語言配置,更改這一配置可以一鍵設置子組件的語言包。如何實現的這一功能呢?
React中,如果不想通過逐層傳遞props或者state的方式來傳遞數據,不如考慮考慮Context。
1. React Context共享上下文數據
借助Context可以實現跨層級的組件數據傳遞。
它的使用場景是生產者消費者模式,在上面的例子中,<ConfigProvider>
就是生產者,<Search> <Dialog>
組件就是消費者。 他們分別通過一系列屬性方法(childContextTypes屬性 getChildContext方法
/contextTypes屬性
),建立起一條通信線。
// 生產者
class ConfigProvider extends React.Component {// 聲明Context對象static childContextTypes = {nextLocale: PropTypes.object}// 返回Context對象getChildContext () {return {nextLocale: {}}}render () {return this.props.children;}
}
復制代碼
// 消費者
import zhCN from 'zh-cn';class Search extends React.Component {static propTypes = {locale: PropTypes.object};static defaultProps = {locale: zhCN.Search};// 聲明需要使用的Context屬性static contextTypes = {nextLocale: PropTypes.object};render() {const locale = Object.assign({}, nextLocale['Search'], locale);return (<div><input /><button>{locale.search}</button><button>{locale.clear}</button></div>);}
}export default Search;
復制代碼
這樣,直接給<ConfigProvider>
傳遞國際化參數,就可以改變其子組件所使用的語言包。
數據傳遞的問題解決了,按照這個思路對組件進行改造就可以完美支持一鍵切換語言了~ 事實上,這個解決方案通用性很強,只要子組件(包括自定義組件)都按上面的方式進行改造,就可以支持語言包的切換。
但同時我們也發現,改造工作高度重復,都是新增contextTypes靜態屬性、對props和context上的locale進行merge。有沒有對開發者(基礎組件開發者、業務組件開發者)更友好的方式來降低這部分重復性工作呢?
2.子組件的統一處理
Fusion為Util類組件ConfigProvider增加了一個靜態方法ConfigProvider.config(Component)
,在這個函數里進行對于locale的改造工作,它返回一個新的受控制的高階組件(HOC)NewComponent。
NewComponent 相當于被 ConfigProvider 代理了一層。
在ConfigProvider.config()這個函數里
- 為組件新增contextTypes靜態屬性,以便接收來自父組件的context;
- 為組件props、context傳入的locale進行merge,以便分發處理語言包文案;
這樣,只要子組件經過該函數處理,就可以讓ConfigProvider
“遙控”語言包切換
import zhCN from 'zh-cn';class Search extends React.Component {static propTypes = {locale: PropTypes.object};static defaultProps = {locale: zhCN.Search};render() {return (<div><input /><button>{locale.search}</button><button>{locale.clear}</button></div>);}
}
// 經過統一處理
export default ConfigProvider.config(Search);
復制代碼
ConfigProvider.config(Component)的語言包文案分發處理邏輯簡化如下:
// ConfigProvider.jsx
function config(Component) {class ConfigedComponent extends React.Component {static propTypes = {...(Component.propTypes || {}),locale: PropTypes.object,};static contextTypes = {...(Component.contextTypes || {}),nextLocale: PropTypes.object,};render() {// 組件props上直接設置const { locale } = this.props;// ConfigProvider"遙控"設置const { nextLocale = {} } = this.context;// 組件上直接設置語言包,優先級高于在父組件ConfigProvider上設置。const newLocale = Object.assign({},nextLocale[Component.displayName],locale);return (<Component locale={newLocale}/>);}}return ConfigedComponent;
}
復制代碼
這樣就基本完成了組件庫的多語言能力建設,這也是Fusion Next組件庫的多語言支持的思路。
除此之外,ConfigProvider還有內置了其他通用能力,例如組件的鏡像反轉RTL,pure render開關、修改樣式的默認前綴等,更多可以查看 ConfigProvider源代碼 和 使用文檔 了解。
相關鏈接
- Fusion 多語言切換demo: codepen.io/aboutblank/…
- Fusion ConfigProvider: fusion.design/component/c…
- github: github.com/alibaba-fus…