前端译趣

React国际化实现

2018-05-24  本文已影响4人  linc2046
React国际化实现

React国际化实现

如果你的代码不支持翻译界面文字,像国际化,i18n, 本地化这些都会变得很麻烦。

写React有个好处,可以把界面文字硬编码到js中。
但如果你的网站或应用需要多语言支持, 这就不太好。
即使你只支持一种语言,下面的方法也能允许无需重新编译React源码改变文本。

以下是CodePen上的示例

以下是我使用的方法记录:

设计State结构

即使应用很小,我想遵守redux设计原则。当然,模板代码设计起来有点冗长,但调试的时候会很方便。

我的state中会有两个属性:

{
  "lang": "en",
  "i18n": {}
}

lang属性是使用的语言标识,名字可以随意,但模拟 HTML Lang attribute
会显得有逻辑点。

i18n属性映射每个组件名称到key属性中。

使用以下示例

"i18n": {
  "en": {
    "Menu": {
      "desc": "This app is translated into %1$d languages:",
      "enButton": "English",
      "deButton": "German",
      "esButton": "Spanish"
    }
  },
  "de": {
    "Menu": {
      "desc": "Diese App ist in %1$d Sprachen übersetzt:",
      "enButton": "Englisch",
      "deButton": "Deutsche",
      "esButton": "Spanisch"
    }
  }
}

可能我的德语不是那么准确,但已经知道实现方法了。
如果像desc这样的属性需要引入变量,格式化工具可以用sprinf.
sprinf是一个著名的小库。这还有点必要,因为不同语言的语句结构是变化的。
我要是在组件内手动拼接字符串和变量,那肯定不行。

映射state至props

翻译文本写好了就可以连接组件了。继续上面的例子,我写了个小组件:


const Menu = props => (
  <div>
    <p>This app is translated into {props.langCount} languages:</p>
    <button>English</button>
    <button>German</button>
    <button>Spanish</button>
  </div>
);

所有文本都是硬编码, langCount也不灵活。
我们更新下组件,让它可以接收可翻译的字符串。


const Menu = props => (
  <div>
    <p>{sprintf(props.i18n.desc, props.langCount)}</p>
    <button>{props.i18n.enButton}</button>
    <button>{props.i18n.deButton}</button>
    <button>{props.i18n.esButton}</button>
  </div>
);

现在我们已经有一个无状态组件。我们需要创建一个容器来映射redux状态到这些属性上。


const mapStateToProps = state => ({
  langCount: Object.keys(state.i18n).length
});

const MenuContainer = translate(
  'Menu',
  mapStateToProps
)(Menu);

你会看到我没用react-redux里面的connect方法。我写了个类似定制 translate函数, 接收组件名称作为第一个参数。
这些函数都是高阶组件 HOC


高阶组件是接收组件并返回新组件的函数。

下面是translate的完整源码:


function translate(name, mapStateToProps, mapDispatchToProps) {
  return WrappedComponent => {

    // 定义包装组件
    const TranslatedComponent = props => {

      // 查找翻译或使用默认属性
      props.i18n = props.i18n.hasOwnProperty(props.lang)
        ? props.i18n[props.lang][name]
        : undefined;
      return <WrappedComponent {...props} />;
    };

    // 设置调试名称
    TranslatedComponent.displayName = `Translate(${WrappedComponent.displayName ||
      WrappedComponent.name ||
      'Component'})`;

    // 返回连接到state的高阶组件
    return connect(
      state => ({
        ...(mapStateToProps ? mapStateToProps(state) : {}),
        lang: state.lang,
        i18n: state.i18n
      }),
      dispatch => ({
        ...(mapDispatchToProps ? mapDispatchToProps(dispatch) : {})
      })
    )(TranslatedComponent);
  };
}

第一个参数 name 指向 state翻译数据的对象的key。
可以写成任意值,但我发现简单映射组件名称很方便。

基本上,translate函数是用来实现国际化的方便包装函数。

并不是所有组件都需要直接连接redux状态,如果组件可复用或者上下文变化,更不必实际这么做。

这些场景里我会连接父组件来传递相应的i18n属性。

下面例子中如果button组件是最直接的的react组件,我这样写:


<CustomButton label={props.i18n.enButton}>

至此我已经完成translate的基本实现。
后面可以扩展之后提供更高级的翻译处理逻辑。
或者你可以必要的时候访问connect()的mergeProps方法。

总结

本文实现下面的结果:

你可以看看 codepen示例, 会看到通过mapDispatchToProps 改变语言的redux action。

你可以采取多种方式引入 state.i18n:

这是一个简单且实用的方案。
即使你只支持一种语言,像这样抽象UI文本也有用。

同样可以接入CMS来提供翻译。
你甚至可以检测用户默认语言来设置初始状态。

其实也有很多高级的解决方案。
但我发现小型app用redux和简单connect方法抽象很简单、合适。

译者注

上一篇 下一篇

猜你喜欢

热点阅读