美文网首页前端译趣
React国际化实现

React国际化实现

作者: linc2046 | 来源:发表于2018-05-24 10:05 被阅读4次
    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方法。

    总结

    本文实现下面的结果:

    • 所有用户界面文本抽象至一处
    • 编辑UI文本不需要每个组件都搜索一遍
    • UI文本可以翻译成多种语言
    • state.lang定义了当前使用的语言

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

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

    • 导入json文件编译成js
    • 导出json到全局对象
    • 通过调用API加载json

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

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

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

    译者注

    • 原文链接

    • 因译者水平有限,如有错误,欢迎指正交流

    相关文章

      网友评论

        本文标题:React国际化实现

        本文链接:https://www.haomeiwen.com/subject/mkqdjftx.html