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

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

我的问题不是单纯的技术性问题,而是纯粹的技术问题,而不是纯粹的技术问题,而是人们在生产中用来解决这个问题的模式。 我找不到任何“食谱”,所以我转向我最喜欢的Q / A网站:)

这是我的要求(他们真的是“标准”):

  • 用户可以select语言(微不足道)
  • 在改变语言时,界面应该自动翻译成新的选定语言
  • 我现在不太担心格式化数字,date等,我想要一个简单的解决scheme来转换string

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

每个组件单独处理翻译

这意味着每个组件都有一个en.json,fr.json等文件和翻译后的string。 还有一个帮助函数,帮助读取取决于所选语言的值。

  • Pro:更加尊重React哲学,每个组件都是“独立的”
  • 缺点:你不能集中在一个文件中的所有翻译(例如让别人添加一个新的语言)
  • 缺点:你仍然需要通过当前的语言作为道具,在每一个血腥的组件和他们的孩子

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

所以他们不知道当前的语言,他们只是把一个string列表当作正确匹配当前语言的道具

  • Pro:由于这些string是“从顶部”来的,所以它们可以集中在某个地方
  • 缺点:每个组件现在都绑定到翻译系统中,不能只重用一个,每次都需要指定正确的string

你绕过道具一点,可能使用上下文 thingy传递当前的语言

  • Pro:大多数情况下是透明的,不必一直通过道具传递当前的语言和/或翻译
  • 缺点:使用起来很麻烦

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

你怎么做呢?

在尝试了一些解决scheme之后,我想我find了一个很好的解决scheme,应该是React 0.14的一个惯用的解决scheme(也就是说,它不使用mixins,而是高阶组件)。 )。

所以这里的解决scheme,从底部开始(单个组件):

组件

你的组件唯一需要的(按照惯例)是一个strings道具。 它应该是一个包含你的组件需要的各种string的对象,但是它的形状真的取决于你。

它包含了默认的翻译,所以你可以在别的地方使用这个组件,而不需要提供任何翻译(在这个例子中,它可以使用默认的语言,英文)

 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是一个高级组件,它包装了你的组件,并提供了一些额外的function(这种构造取代了以前版本的React混合)。

第一个参数是一个将用于在翻译文件中查找翻译的键(我在这里使用了组件的名称,但它可以是任何东西)。 第二个(注意,该函数是curry,允许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; }; } 

这不是魔术:它只会从上下文中读取当前的语言(并且上下文不会遍布整个代码库,只是在这个包装器中使用),然后从加载的文件中获取相关的string对象。 这个逻辑在这个例子中是相当天真的,可以以你想要的方式完成。

重要的一点是,它从上下文中获取当前的语言并将其转换为string,只要提供了密钥。

在层次结构的最顶端

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

你们有什么感想?

我认为是解决了所有的问题,我试图避免在我的问题:翻译逻辑不stream血的源代码,它是相当孤立的,并允许重新使用组件没有它。

例如,MyComponent不需要被translate()包装,并且可以是单独的,允许任何人按照自己的意思提供strings

[编辑:31/03/2016]:我最近在回溯董事会(敏捷回顾)工作,与React&Reduxbuild成,是多语言。 由于相当多的人在评论中要求一个真实的例子,这里是:

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

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

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

2-当用户更改语言时,可以更改整个应用程序语言,而无需刷新UI。

3-当用户改变语言,这也将允许你从API,本地文件,甚至从常量检索新的语言

4-您还可以保存其他重要的东西,如时区,货币,方向(RTL / LTR)和可用语言列表

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

6-你可以在一个地方有你的后端和前端string,例如在我的情况下,我使用i18n节点进行本地化,当用户改变ui语言,我只是做一个正常的API调用,在后端我只是返回i18n.getCatalog(req)这将只返回当前语言的所有用户string

我对国际初始状态的build议是:

 { "language":"ar", "availableLanguages":[ {"code":"en","name": "English"}, {"code":"ar","name":"عربي"} ], "catalog":[ "Hello":"مرحباً", "Thank You":"شكراً", "You have {count} new messages":"لديك {count} رسائل جديدة" ], "timezone":"", "currency":"", "direction":"rtl", } 

国际化的额外有用的模块:

1- string模板这将允许您在您的目录string之间插入值,例如:

 import template from "string-template"; const count = 7; //.... template(i18n.catalog["You have {count} new messages"],{count}) // لديك ٧ رسائل جديدة 

2- 人格式这个模块可以让你把一个数字转换成一个可读的string,例如:

 import humanFormat from "human-format"; //... humanFormat(1337); // => '1.34 k' // you can pass your own translated scale, eg: humanFormat(1337,MyScale) 

3- 时刻是最着名的date和时间npm库,你可以翻译的时刻,但它已经有一个内置的翻译,只需要通过当前状态语言,例如:

 import moment from "moment"; const umoment = moment().locale(i18n.language); umoment.format('MMMM Do YYYY, h:mm:ss a'); // أيار مايو ٢ ٢٠١٧، ٥:١٩:٥٥ م 

安托万的解决scheme工作正常,但有一些注意事项:

  • 它直接使用React上下文,当我使用Redux的时候,我倾向于避免这种情况
  • 它直接从文件中导入短语,如果您想在运行时(客户端)获取所需的语言,则可能会产生问题
  • 它不使用任何i18n库,这是轻量级的,但不能让您访问像复数化和插值等方便的翻译function

这就是为什么我们在Redux和AirBNB的Polyglot之上构build了多语言多语言的原因。
(我是其中一位作者)

它提供 :

  • 还原器在您的Redux商店中存储语言和相应的消息。 您可以通过以下任一方式提供:
    • 一个中间件,你可以configuration捕捉特定的行动,扣除当前的语言和获取/获取相关的消息。
    • 直接调度setLanguage(lang, messages)
  • 一个getP(state)select器,检索一个P对象,公开了4个方法:
    • t(key) :原来的polyglot Tfunction
    • tc(key) :大写翻译
    • tu(key) :大写翻译
    • tm(morphism)(key) :自定义变形翻译
  • 一个getLocale(state)select器来获取当前语言
  • 通过在道具中注入p对象来translate更高阶的组件来增强您的React组件

简单的用法示例:

派遣新的语言:

 import setLanguage from 'redux-polyglot/setLanguage'; store.dispatch(setLanguage('en', { common: { hello_world: 'Hello world' } } } })); 

在组件中:

 import React, { PropTypes } from 'react'; import translate from 'redux-polyglot/translate'; const MyComponent = props => ( <div className='someId'> {props.pt('common.hello_world')} </div> ); MyComponent.propTypes = { p: PropTypes.shape({t: PropTypes.func.isRequired}).isRequired, } export default translate(MyComponent); 

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

从我的研究来看,在JavaScript, ICU和gettext中似乎有两种主要的方法用于国际化。

我只使用gettext,所以我有点偏见。

令我惊奇的是,这种支持有多差。 我来自PHP世界,无论是CakePHP或WordPress。 在这两种情况下,这是一个基本的标准,所有的string都被__('')包围,然后再使用PO文件非常容易地进行翻译。

gettext的

您可以熟悉sprintf格式化string,并且PO文件将被成千上万的不同机构轻松转换。

有两个受欢迎的选项:

  1. i18next ,用arkency.com博客文章描述的用法
  2. 杰德 ,用sentry.io文章描述的用法和这个React + Redux的post ,

两者都支持gettext风格,sprintf风格的string格式和导入/导出到PO文件。

i18next有自己开发的React扩展 。 杰德不。 Sentry.io似乎使用Jed和React的自定义集成。 React + Redux后 ,build议使用

工具:jed + po2json + jsxgettext

然而,Jed似乎更像是一个专注于gettext的实现 – 也就是它expression的意图,其中i18next只是它的一个选项。

ICU

这对翻译的边缘情况有更多的支持,例如处理性别问题。 如果你有更复杂的语言可以翻译成英文,我想你会看到这个好处。

一个stream行的select是messageformat.js 。 在这个sentry.io博客教程中简要讨论。 messageformat.js实际上是由写Jed的同一个人开发的。 他对使用ICU提出了相当强烈的要求 :

杰德在我看来是function完备。 我很乐意修复错误,但是通常我不感兴趣向库中添加更多内容。

我也维护messageformat.js。 如果你不特别需要一个gettext实现,我可能会build议使用MessageFormat,因为它更好地支持复数/性别并且具有内置的区域设置数据。

粗略的比较

带sprintf的gettext:

 i18next.t('Hello world!'); i18next.t( 'The first 4 letters of the english alphabet are: %s, %s, %s and %s', { postProcess: 'sprintf', sprintf: ['a', 'b', 'c', 'd'] } ); 

messageformat.js(阅读指南中的最佳猜测):

 mf.compile('Hello world!')(); mf.compile( 'The first 4 letters of the english alphabet are: {s1}, {s2}, {s3} and {s4}' )({ s1: 'a', s2: 'b', s3: 'c', s4: 'd' }); 

如果还没有看到https://react.i18next.com/可能是一个很好的build议。; 它基于i18next:学习一次 – 随处翻译。

你的代码看起来像这样:

 <div>{t('simpleContent')}</div> <Trans i18nKey="userMessagesUnread" count={count}> Hello <strong title={t('nameTitle')}>{{name}}</strong>, you have {{count}} unread message. <Link to="/msgs">Go to messages</Link>. </Trans> 

来自样本:

  • 的WebPack
  • CRA
  • expo.js
  • next.js
  • 故事书集成
  • 拉扎勒
  • DAT

https://github.com/i18next/react-i18next/tree/master/example

除此之外,您还应该在开发期间考虑工作stream程,稍后再考虑翻译人员 – > https://www.youtube.com/watch?v=9NOzJhgmyQE

Interesting Posts