Fusion元件庫是如何支援多語言能力的

FuisonDesign發表於2019-02-19

隨著國際化發展,多語言的需求越來越常見,單一的語言已經遠不能滿足需求了。作為一個元件庫,支援多語言也是基本能力。 多語言功能的本質其實是文字的替換,一個詞彙“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引數,在語言切換時改變值的內容。

並且語言的設定大都是以專案(或者頁面)為單位的,有沒有更聰明、對開發者更友好的使用方式呢?

一鍵設定語言

undefined
如果你使用過Fusion Next或者體驗過多語言demo,就可以發現使用方式是這樣的:

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原始碼使用文件 瞭解。

相關連結

相關文章