小能豆

构建一个需要支持多种语言和语言环境的应用程序。

javascript

我的问题并非纯技术性的,而是关于架构,以及人们在生产中实际使用的解决这个问题的模式。我找不到任何关于这个问题的“手册”,所以我转向了我最喜欢的问答网站 :)

以下是我的要求(它们确实是“标准”):

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

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

每个组件单独处理翻译

这意味着每个组件都有一组 en.json、fr.json 等文件,其中包含翻译的字符串。还有一个辅助函数,用于帮助读取取决于所选语言的值。

  • 优点:更加尊重 React 哲学,每个组件都是“独立的”
  • 缺点:您无法将所有翻译集中在一个文件中(例如让其他人添加新语言)
  • 缺点:你仍然需要在每个组件及其子组件中将当前语言作为 prop 进行传递

每个组件通过 props 接收翻译

因此,它们不知道当前的语言,它们只是将恰好与当前语言匹配的字符串列表作为 props

  • 优点:由于这些字符串是“来自顶部”的,因此它们可以集中在某个地方
  • 缺点:每个组件现在都与翻译系统绑定,你不能只重复使用一个,每次都需要指定正确的字符串

你可以稍微绕过 props,并可能使用context来传递当前语言

  • 优点:它基本上是透明的,不必一直通过 props 传递当前语言和/或翻译
  • 缺点:使用起来看起来很麻烦

阅读 36

收藏
2024-06-10

共1个答案

小能豆

在尝试了相当多的解决方案之后,我想我找到了一个效果很好的解决方案,它应该是 React 0.14 的惯用解决方案(即它不使用 mixins,而是使用高阶组件)(编辑:当然也完全适用于 React 15!)。

因此,这里是解决方案,从底部开始(各个组件):

组件

您的组件唯一需要的东西(按照惯例)是stringsprops。它应该是一个包含您的组件所需的各种字符串的对象,但实际上它的形状由您决定。

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

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);

高阶组件

在前面的代码片段中,你可能注意到了最后一行: translate('MyComponent')(MyComponent)

translate在这种情况下,它是一个包装你的组件的高阶组件,并提供一些额外的功能(这种构造取代了以前版本的 React 中的 mixin)。

第一个参数是用于在翻译文件中查找翻译的键(我在这里使用了组件的名称,但它可以是任何名称)。第二个参数(请注意,该函数是柯里化的,以允许 ES7 装饰器)是要包装的组件本身。

以下是翻译组件的代码:

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;
    };
}

这不是魔术:它只会从上下文中读取当前语言(并且该上下文不会渗透到整个代码库中,只是在此包装器中使用),然后从加载的文件中获取相关的字符串对象。此示例中的这部分逻辑非常简单,可以按照您想要的方式完成。

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

处于层级的最顶端

在根组件上,您只需从当前状态设置当前语言即可。以下示例使用 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);

最后,翻译文件:

翻译文件

// 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'
    }
};

你们有什么感想?

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

例如,MyComponent 不需要被 Translation() 包装,而是可以分离,从而允许任何希望strings以自己的方式提供它的人重复使用。

[编辑:2016 年 3 月 31 日]:我最近在开发一个回顾板(用于敏捷回顾),它使用 React 和 Redux 构建,并且支持多种语言。由于很多人在评论中要求提供一个真实示例,因此这里是示例:

您可以在此处找到代码:https://github.com/antoinejaussoin/retro-board/tree/master

2024-06-10