React/Redux和多语言(国际化)应用程序 - 架构

Ant*_*oin 114 architecture translation internationalization reactjs redux

我正在构建一个需要以多种语言和语言环境提供的应用程序.

我的问题不仅仅是技术问题,而是关于架构,以及人们实际在生产中用来解决这个问题的模式.我找不到任何"食谱",所以我转向我最喜欢的Q/A网站:)

这是我的要求(它们真的是"标准"):

  • 用户可以选择语言(平凡)
  • 更改语言后,界面应自动转换为新选择的语言
  • 我现在不太担心格式化数字,日期等,我想要一个简单的解决方案来翻译字符串

以下是我可以想到的可能解决方案:

每个组件都孤立地处理翻译

这意味着每个组件具有例如一组en.json,fr.json等文件以及与翻译的字符串一起的文件.还有一个辅助函数可以帮助读取取决于所选语言的值.

  • 亲:更尊重React的理念,每个组件都是"独立的"
  • 缺点:您无法将所有翻译集中在一个文件中(例如,让其他人添加新语言)
  • 缺点:您仍然需要将当前语言作为道具传递给每个血腥组件及其子项

每个组件通过道具接收翻译

所以他们不知道当前的语言,他们只是将字符串列表作为与当前语言匹配的道具

  • 亲:由于这些字符串"从顶部"出现,它们可以集中在某处
  • 缺点:每个组件现在都绑定到翻译系统,你不能只重用一个,你需要每次都指定正确的字符串

你稍微绕过了道具,并可能使用上下文来传递当前语言

  • 亲:它大部分是透明的,不必一直通过道具传递当前的语言和/或翻译
  • 缺点:使用它看起来很麻烦

如果您有任何其他想法,请说!

你怎么做呢?

Ant*_*oin 107

在尝试了不少解决方案之后,我想我找到了一个效果很好并且应该是React 0.14的惯用解决方案(即它不使用mixins,但是高阶组件)(编辑:当然,React 15也完全没问题!) ).

所以这里的解决方案,从底部开始(各个组件):

组件

您的组件唯一需要的(按惯例)是strings道具.它应该是一个包含Component所需的各种字符串的对象,但实际上它的形状取决于你.

它确实包含默认翻译,因此您可以在其他地方使用该组件而无需提供任何翻译(它可以使用默认语言开箱即用,本例中为英语)

import { default as React, PropTypes } from 'react';
import translate from './translate';

class MyComponent extends React.Component {
    render() {

        return (
             <div>
                { this.props.strings.someTranslatedText }
             </div>
        );
    }
}

MyComponent.propTypes = {
    strings: PropTypes.object
};

MyComponent.defaultProps = {
     strings: {
         someTranslatedText: 'Hello World'
    }
};

export default translate('MyComponent')(MyComponent);
Run Code Online (Sandbox Code Playgroud)

高阶组件

在上一个代码段中,您可能已在最后一行注意到这一点: translate('MyComponent')(MyComponent)

translate 在这种情况下,是一个包含组件的高阶组件,并提供一些额外的功能(这种结构取代了以前版本的React的混合).

第一个参数是一个用于查找翻译文件中翻译的密钥(我在这里使用了组件的名称,但它可以是任何东西).第二个(注意函数是curryed,允许ES7装饰器)是要包装的Component本身.

以下是translate组件的代码:

import { default as React } from 'react';
import en from '../i18n/en';
import fr from '../i18n/fr';

const languages = {
    en,
    fr
};

export default function translate(key) {
    return Component => {
        class TranslationComponent extends React.Component {
            render() {
                console.log('current language: ', this.context.currentLanguage);
                var strings = languages[this.context.currentLanguage][key];
                return <Component {...this.props} {...this.state} strings={strings} />;
            }
        }

        TranslationComponent.contextTypes = {
            currentLanguage: React.PropTypes.string
        };

        return TranslationComponent;
    };
}
Run Code Online (Sandbox Code Playgroud)

这不是魔术:它只是从上下文中读取当前语言(并且该上下文不会在整个代码库中流失,仅在此包装器中使用),然后从加载的文件中获取相关的字符串对象.在这个例子中,这条逻辑非常天真,可以按照你想要的方式完成.

重要的是它根据提供的密钥从上下文中获取当前语言并将其转换为字符串.

在层次结构的最顶层

在根组件上,您只需要从当前状态设置当前语言.以下示例使用Redux作为类似Flux的实现,但可以使用任何其他框架/模式/库轻松转换它.

import { default as React, PropTypes } from 'react';
import Menu from '../components/Menu';
import { connect } from 'react-redux';
import { changeLanguage } from '../state/lang';

class App extends React.Component {
    render() {
        return (
            <div>
                <Menu onLanguageChange={this.props.changeLanguage}/>
                <div className="">
                    {this.props.children}
                </div>

            </div>

        );
    }

    getChildContext() {
        return {
            currentLanguage: this.props.currentLanguage
        };
    }
}

App.propTypes = {
    children: PropTypes.object.isRequired,
};

App.childContextTypes = {
    currentLanguage: PropTypes.string.isRequired
};

function select(state){
    return {user: state.auth.user, currentLanguage: state.lang.current};
}

function mapDispatchToProps(dispatch){
    return {
        changeLanguage: (lang) => dispatch(changeLanguage(lang))
    };
}

export default connect(select, mapDispatchToProps)(App);
Run Code Online (Sandbox Code Playgroud)

并完成,翻译文件:

翻译文件

// en.js
export default {
    MyComponent: {
        someTranslatedText: 'Hello World'
    },
    SomeOtherComponent: {
        foo: 'bar'
    }
};

// fr.js
export default {
    MyComponent: {
        someTranslatedText: 'Salut le monde'
    },
    SomeOtherComponent: {
        foo: 'bar mais en français'
    }
};
Run Code Online (Sandbox Code Playgroud)

你们有什么感想?

我认为在我的问题中解决了我试图避免的所有问题:翻译逻辑不会在整个源代码中流失,它是相当孤立的,并且允许在没有它的情况下重用组件.

例如,MyComponent不需要被translate()包装,并且可以是独立的,允许任何希望strings通过自己的意思提供它的人重用它.

[编辑:31/03/2016]:我最近参与了一个Retrospective Board(用于敏捷回顾),使用React和Redux构建,并且是多语言的.由于很多人在评论中要求一个真实的例子,这里是:

你可以在这里找到代码:https://github.com/antoinejaussoin/retro-board/tree/master

  • 有没有理由为什么你没有尝试react-intl? (6认同)
  • 实际上,我发现它能够完美地工作(满足我的需求).默认情况下,它使组件在没有转换的情况下工作,并且转换只是在它之上,而组件没有意识到它 (2认同)

Far*_*uti 18

根据我的经验,最好的方法是创建一个i18n redux状态并使用它,原因有很多:

1-这将允许您从数据库,本地文件或甚至模板引擎(如ejs或jade)传递初始值

2-当用户更改语言时,您甚至无需刷新UI即可更改整个应用程序语言.

3-当用户更改语言时,这也允许您从api,本地文件甚至常量中检索新语言

4-您还可以使用时区,货币,方向(RTL/LTR)和可用语言列表等字符串保存其他重要内容

5-您可以将更改语言定义为正常的redux操作

6-您可以将后端和前端字符串放在一个位置,例如在我的情况下,我使用i18n-node进行本地化,当用户更改ui语言时,我只是进行正常的api调用,而在后端我只返回i18n.getCatalog(req)此将仅返回当前语言的所有用户字符串

我对i18n初始状态的建议是:

{
  "language":"ar",
  "availableLanguages":[
    {"code":"en","name": "English"},
    {"code":"ar","name":"????"}
  ],
  "catalog":[
     "Hello":"??????",
     "Thank You":"?????",
     "You have {count} new messages":"???? {count} ????? ?????"
   ],
  "timezone":"",
  "currency":"",
  "direction":"rtl",
}
Run Code Online (Sandbox Code Playgroud)

i18n的额外有用模块:

1- string-template这将允许您在目录字符串之间注入值,例如:

import template from "string-template";
const count = 7;
//....
template(i18n.catalog["You have {count} new messages"],{count}) // ???? ? ????? ?????
Run Code Online (Sandbox Code Playgroud)

2- 人格式此模块允许您将数字转换为人类可读字符串,例如:

import humanFormat from "human-format";
//...
humanFormat(1337); // => '1.34 k'
// you can pass your own translated scale, e.g: humanFormat(1337,MyScale)
Run Code Online (Sandbox Code Playgroud)

3- momentjs最着名的日期和时间npm库,你可以翻译时刻,但它已经有一个内置的翻译只需你传递当前的状态语言,例如:

import moment from "moment";

const umoment = moment().locale(i18n.language);
umoment.format('MMMM Do YYYY, h:mm:ss a'); // ???? ???? ? ????? ?:??:?? ?
Run Code Online (Sandbox Code Playgroud)

  • 我建议i18n目录为redux状态,看来你不懂redux (9认同)

Jal*_*lil 5

Antoine的解决方案运行良好,但有一些警告:

  • 它直接使用React上下文,我在使用Redux时往往会避免使用它
  • 它直接导入文件中的短语,如果您想在运行时(客户端)获取所需的语言,则可能会出现问题
  • 它不使用任何i18n库,它是轻量级的,但不允许您访问多元化和插值等便捷的翻译功能

这就是我们在Redux和AirBNB的Polyglot之上构建redux-polyglot的原因. (我是其中一位作者)

它提供 :

  • 一个reducer,用于在Redux商店中存储语言和相应的消息.您可以通过以下两种方式提供:
    • 您可以配置一个中间件来捕获特定操作,扣除当前语言并获取/获取相关消息.
    • 直接派遣 setLanguage(lang, messages)
  • 一个getP(state)选择器,用于检索P公开4个方法的对象:
    • t(key):原始多语言T功能
    • tc(key):大写翻译
    • tu(key):大写翻译
    • tm(morphism)(key):自定义变形翻译
  • getLocale(state)获取当前语言的选择器
  • 一个translate更高阶的组件,通过p在props中注入对象来增强React组件

简单用法示例:

派遣新语言:

import setLanguage from 'redux-polyglot/setLanguage';

store.dispatch(setLanguage('en', {
    common: { hello_world: 'Hello world' } } }
}));
Run Code Online (Sandbox Code Playgroud)

在组件中:

import React, { PropTypes } from 'react';
import translate from 'redux-polyglot/translate';

const MyComponent = props => (
  <div className='someId'>
    {props.p.t('common.hello_world')}
  </div>
);
MyComponent.propTypes = {
  p: PropTypes.shape({t: PropTypes.func.isRequired}).isRequired,
}
export default translate(MyComponent);
Run Code Online (Sandbox Code Playgroud)

如果您有任何问题/建议,请告诉我!