我正在构建一个应用程序,它将需要提供多种语言和区域设置。
我的问题不是纯粹的技术问题,而是关于架构以及人们在生产中实际用来解决此问题的模式。我在那里找不到任何“食谱”,所以我转向了我最喜欢的问答网站:)
这是我的要求(它们实际上是“标准”):
这是我可以考虑的可能解决方案:
每个组件都独立处理翻译
这意味着每个组件在其旁边都有例如一组en.json,fr.json等文件以及翻译后的字符串。还有一个帮助程序功能,可帮助您根据所选语言从这些值中读取值。
每个组成部分都通过道具接收翻译
所以他们不知道当前的语言,他们只是拿一个字符串列表作为道具,恰好匹配当前的语言
您绕过道具了一下,可能是使用 情境啄向下传递当前语言
如果您还有其他想法,请说!
你怎么做呢?
在尝试了很多解决方案之后,我认为我找到了一个很好的解决方案,并且应该是React 0.14的惯用解决方案(即,它不使用mixins,而是使用高阶组件)( 编辑 :当然,在React 15中也很好! )。
因此,这里是从底部开始的解决方案(各个组件):
组件
根据约定,您的组件唯一需要的是strings道具。它应该是一个包含Component所需的各种字符串的对象,但实际上它的形状取决于您。
strings
它确实包含默认翻译,因此您可以在其他地方使用该组件而无需提供任何翻译(它可以使用默认语言(本例中为英语)开箱即用。
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('MyComponent')(MyComponent)
translate 在这种情况下,是一个高阶组件,它包装了您的组件,并提供了一些额外的功能(此构造替代了React早期版本的mixins)。
translate
第一个参数是一个密钥,将用于在翻译文件中查找翻译(我在这里使用了组件的名称,但是可以是任何东西)。第二个(注意,该函数已进行咖喱化,以允许使用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不需要被translate()包裹,可以将其分隔开,从而允许其他希望strings按自己的意愿提供的人重用。
[Edit:31/03/2016]:我最近在一个Reactspective Board(用于敏捷回顾)上工作,它是由React&Redux构建的,并且是多语言的。由于很多人在评论中要求提供真实的示例,因此它是: