React国际化实现
如果你的代码不支持翻译界面文字,像国际化,i18n, 本地化这些都会变得很麻烦。
写React有个好处,可以把界面文字硬编码到js中。
但如果你的网站或应用需要多语言支持, 这就不太好。
即使你只支持一种语言,下面的方法也能允许无需重新编译React源码改变文本。
以下是CodePen上的示例
以下是我使用的方法记录:
设计State结构
即使应用很小,我想遵守redux设计原则。当然,模板代码设计起来有点冗长,但调试的时候会很方便。
我的state中会有两个属性:
{
"lang": "en",
"i18n": {}
}
lang属性是使用的语言标识,名字可以随意,但模拟 HTML Lang attribute
会显得有逻辑点。
i18n属性映射每个组件名称到key属性中。
使用以下示例
"i18n": {
"en": {
"Menu": {
"desc": "This app is translated into %1$d languages:",
"enButton": "English",
"deButton": "German",
"esButton": "Spanish"
}
},
"de": {
"Menu": {
"desc": "Diese App ist in %1$d Sprachen übersetzt:",
"enButton": "Englisch",
"deButton": "Deutsche",
"esButton": "Spanisch"
}
}
}
可能我的德语不是那么准确,但已经知道实现方法了。
如果像desc这样的属性需要引入变量,格式化工具可以用sprinf.
sprinf是一个著名的小库。这还有点必要,因为不同语言的语句结构是变化的。
我要是在组件内手动拼接字符串和变量,那肯定不行。
映射state至props
翻译文本写好了就可以连接组件了。继续上面的例子,我写了个小组件:
const Menu = props => (
<div>
<p>This app is translated into {props.langCount} languages:</p>
<button>English</button>
<button>German</button>
<button>Spanish</button>
</div>
);
所有文本都是硬编码, langCount也不灵活。
我们更新下组件,让它可以接收可翻译的字符串。
const Menu = props => (
<div>
<p>{sprintf(props.i18n.desc, props.langCount)}</p>
<button>{props.i18n.enButton}</button>
<button>{props.i18n.deButton}</button>
<button>{props.i18n.esButton}</button>
</div>
);
现在我们已经有一个无状态组件。我们需要创建一个容器来映射redux状态到这些属性上。
const mapStateToProps = state => ({
langCount: Object.keys(state.i18n).length
});
const MenuContainer = translate(
'Menu',
mapStateToProps
)(Menu);
你会看到我没用react-redux里面的connect方法。我写了个类似定制 translate函数, 接收组件名称作为第一个参数。
这些函数都是高阶组件 HOC
高阶组件是接收组件并返回新组件的函数。
下面是translate的完整源码:
function translate(name, mapStateToProps, mapDispatchToProps) {
return WrappedComponent => {
// 定义包装组件
const TranslatedComponent = props => {
// 查找翻译或使用默认属性
props.i18n = props.i18n.hasOwnProperty(props.lang)
? props.i18n[props.lang][name]
: undefined;
return <WrappedComponent {...props} />;
};
// 设置调试名称
TranslatedComponent.displayName = `Translate(${WrappedComponent.displayName ||
WrappedComponent.name ||
'Component'})`;
// 返回连接到state的高阶组件
return connect(
state => ({
...(mapStateToProps ? mapStateToProps(state) : {}),
lang: state.lang,
i18n: state.i18n
}),
dispatch => ({
...(mapDispatchToProps ? mapDispatchToProps(dispatch) : {})
})
)(TranslatedComponent);
};
}
第一个参数 name 指向 state翻译数据的对象的key。
可以写成任意值,但我发现简单映射组件名称很方便。
基本上,translate函数是用来实现国际化的方便包装函数。
并不是所有组件都需要直接连接redux状态,如果组件可复用或者上下文变化,更不必实际这么做。
这些场景里我会连接父组件来传递相应的i18n属性。
下面例子中如果button组件是最直接的的react组件,我这样写:
<CustomButton label={props.i18n.enButton}>
至此我已经完成translate的基本实现。
后面可以扩展之后提供更高级的翻译处理逻辑。
或者你可以必要的时候访问connect()的mergeProps方法。
总结
本文实现下面的结果:
- 所有用户界面文本抽象至一处
- 编辑UI文本不需要每个组件都搜索一遍
- UI文本可以翻译成多种语言
- state.lang定义了当前使用的语言
你可以看看 codepen示例, 会看到通过mapDispatchToProps 改变语言的redux action。
你可以采取多种方式引入 state.i18n:
- 导入json文件编译成js
- 导出json到全局对象
- 通过调用API加载json
这是一个简单且实用的方案。
即使你只支持一种语言,像这样抽象UI文本也有用。
同样可以接入CMS来提供翻译。
你甚至可以检测用户默认语言来设置初始状态。
其实也有很多高级的解决方案。
但我发现小型app用redux和简单connect方法抽象很简单、合适。
译者注
-
因译者水平有限,如有错误,欢迎指正交流
网友评论