我的问题并非纯技术性的,而是关于架构,以及人们在生产中实际使用的解决这个问题的模式。我找不到任何关于这个问题的“手册”,所以我转向了我最喜欢的问答网站 :)
以下是我的要求(它们确实是“标准”):
以下是我能想到的可能的解决方案:
每个组件单独处理翻译
这意味着每个组件都有一组 en.json、fr.json 等文件,其中包含翻译的字符串。还有一个辅助函数,用于帮助读取取决于所选语言的值。
每个组件通过 props 接收翻译
因此,它们不知道当前的语言,它们只是将恰好与当前语言匹配的字符串列表作为 props
你可以稍微绕过 props,并可能使用context来传递当前语言
在尝试了相当多的解决方案之后,我想我找到了一个效果很好的解决方案,它应该是 React 0.14 的惯用解决方案(即它不使用 mixins,而是使用高阶组件)(编辑:当然也完全适用于 React 15!)。
因此,这里是解决方案,从底部开始(各个组件):
组件
您的组件唯一需要的东西(按照惯例)是stringsprops。它应该是一个包含您的组件所需的各种字符串的对象,但实际上它的形状由您决定。
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 中的 mixin)。
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 不需要被 Translation() 包装,而是可以分离,从而允许任何希望strings以自己的方式提供它的人重复使用。
[编辑:2016 年 3 月 31 日]:我最近在开发一个回顾板(用于敏捷回顾),它使用 React 和 Redux 构建,并且支持多种语言。由于很多人在评论中要求提供一个真实示例,因此这里是示例:
您可以在此处找到代码:https://github.com/antoinejaussoin/retro-board/tree/master