前言
- 国际化背景下,前端系统需要支持多语言的场景越来越多。
- 当下已经存在开源的前端多语言解决方案(比如
i18n
)。以React
为例,多语言解决方案有i18n-react
react-intl
等。 - 不管是为了 合理运用 现有解决方案,还是针对自己公司的 特殊性 需要 二次封装,甚至完全 造轮子,了解 多语言设计模式 都很有必要。
思路
- 一套系统,支持多种语言,我们自然的会想到配置化。
N
种语言N
套配置;在页面加载的时候,根据当前语言环境,注入对应的语言配置文件。在业务组件中通过对应的API
读取配置展示文案。
步骤
- 创建和管理多语言资源文件
- 根据语言(
key
)获取资源文件 - 注入多语言资源文件并暴露调用的
API
- 使用多语言
创建和管理多语言资源文件
- 创建:支持
N
种语言,就配置N
套对应的语言文件;通过{ key: value }
的方式组织起来; - 粒度:为了扩展性和可维护性考虑,建议一个大的模块对应一套配置文件。对应特别通用的一些文案,可以配置一套公用配置文件。
- 管理:建议公用配置文件放顶层目录管理,模块配置文件放在模块目录一起管理。这样方便尽量只注入模块需要的配置文件。
//目录结构
.
|-- common
|-- locale
|-- en_US.json
|-- zh_CN.json
|-- pages
|-- moduleA
|-- public
|-- locale
|-- en_US.json
|-- zh_CN.json
|-- moduleB
|-- public
|-- locale
|-- en_US.json
|-- zh_CN.json
// 文件格式
{
"app.moduleA.hello_word": "Hello word",
"app.moduleA.hello_tom": "Hello tom"
...
}
注入多语言资源文件并暴露调用的API
- 获取多语言
key
:根据实际情况,一般来源有:navigator.userAgent
navigator.language
URL参数
等。 - 顶层注入:封装多语言组件 (
LocalProvider
) 包裹业务组件。
// 顶层注入的核心代码...
const lang = comFun.getLang(); // 获取当前环境语言的封装方法
ReactDom.render((
<LocalProvider lang={lang} files={
[
import('@common/locale/'+lang+'.json'), // 公共配置
import('./public/locale/'+lang+'.json') // 模块配置
]
}>
{/* AppContainer: 其他公共封装 */}
<Route path="/" component={AppContainer}>
<IndexRoute component ={Index}/>
<Route path="index" component={Index}/>
{/* ... */}
</Route>
</LocalProvider>
), document.getElementById('app'));
// LocalProvider 核心 & 简易实现...
import React, { Component } from 'react'
/**
* 多语言组件封装
*/
class LocaleProvider extends Component {
constructor(props) {
super(props)
const { files, lang } = this.props
this.Lang = lang
this.transObj = {}; //配置存储
this.state = {
localeReady: files && files.length ? false : true
}
this.translateFile(files, this.Lang)
}
/**
* 加载多语言文件
* @param {Array} imports 异步加载语言文件
* @param {String} lang 当前选择语言
*/
translateFile = (imports, lang) => {
if (imports && imports.length) {
Promise.all(imports)
.then(
modules => {
this.transObj = Object.assign({}, ...modules)
React.Component.prototype.T = this.$T // 方法注入
this.setState({ localeReady: true })
},
_ => {
console.log('require translate file fail')
}
)
.catch(e => {
console.error(e)
})
}
}
/**
* 读取多语言配置
* @param {String} key 具体配置的key
*/
$T = (key) => {
if (typeof key === 'string') {
return this.transObj[key] || key // 找不到key的配置 便返回key
} else {
throw new TypeError('Translate item should be string, but got ' +
typeof key)
}
}
render() {
return <div>{this.state.localeReady && this.props.children}</div>
}
}
export default LocaleProvider
以上只是粗糙的简易实现,实际项目中还需要进行扩展,润色,封装。比如增加文案的
format
功能,可以插入变量。
使用多语言
// 略...
render() {
return (
<div>
{/* 直接调用 LocaleProvider 中注入 react.Component 中的方法 T 使用*/}
{ this.T('app.moduleA.hello_word')}
</div>
)
}
注意:如果要在方法组件中使用,需要把
T
作为props
从Component
父组件传递下去。也可以直接把文案作为props
传递下去。