前言
前段时间公司拓展了海外业务,因此需要前端系统的开发在完成业务需求的同时,可以实现国际化,以满足用户的使用需要。
技术挑战
之前完全没有接触相关的技术,乍一看感觉很是简单,就是翻译一下文案。但是真正着手做的时候发现面临很多技术上的挑战,随着项目上线,所以梳理一下整个开发过程中的思路。
需要进行国际化开发的内容
label、placeholder、字段校验提示信息
第三方插件
公司项目中用到了第三发组件:Antdesign和bootstraptable,都支持国际化开发,但是其中bootstraptable的官方文档对相关的内容较少,所以花费了较多的时间。
开发思路
了解到React有支持国际化的组件-ReactIntl,所以除第三方插件以外,项目中的文案,基本都可以实现。把需要翻译的文案提取出来,放到对应的语言文件(en_US.js)中,因为项目中好多内容是重复的,所以工作量不是很大,如果内容很多的话,这种方式的工作量太大了,需要更换方案。
开发过程
- 安装ReactR-Intl组件
React-intl是雅虎的语言国际化开源项目FormatJS的一部分,通过其提供的组件和API可以与ReactJS绑定。上面这句话援引了官方文档的说辞,主要表达的是,这是一个很好的开源项目,有大团队支持,使用量也很大,不会有太多坑,可以放心用。
React-intl提供了两种使用方法,一种是引用React组件,另一种是直接调取API,官方更加推荐在React项目中使用前者,只有在无法使用React组件的地方,才应该调用框架提供的API,事实上,我在项目的过程大部分都是用API的方式。
- 安装react-intl需要执行下面的命令:
npm install intl --save
- 在项目中引入react-intl
import { FormattedMessage } from 'react-intl';
该组件提供了全面的API来帮助我们进行开发,这里我们不详细讲解各个API,只是介绍如何使用,其中使用频率较高API包括:FormattedMessage(字符串格式化API)、injectIntl(高阶组件)、FormattedPlural(格式化量词)
- 通过代码来了解一下如何使用上述API
import { injectIntl, FormattedMessage } from 'react-intl';
class Mycomponent extends Reflux.Component{
render(){
const {messages, loacal} = this.props.intl;
return (
)
}
}
export default injectIntl(Mycomponent );
通过injectIntl 在我们在 组件的 props 上会得到一个 intl 对象,它提供formatDate、formatTime、formatPlural、formatMessage等方法和locale、formats、messages等属性,这时候我们想要显示字符串,可以使用formatMessage进行字符串的格式化。loacal可以拿到当前代码的语言环境。
可以直接利用messages['id]的方式,可以直接从语言文件中拿到当前语言环境下文档中对应id的字符串。
对于其他的字符串,可以使用formatMessageAPI进行格式化。
这种方式的应用场景还是蛮多的。
- 配置语言文件
语言的配置文件zh-config.js是比较关键的,我们举例说明
import appLocaleData from 'react-intl/locale-data/zh';
import messages from './zh_CN';
window.appLocale = {
messages: Object.assign({}, messages),
locale: 'zh-CN',
data: appLocaleData,
},
};
export default window.appLocale;
下面是语言文件zh.js,即key-value键值对
const zh_CN = {
id: "中文字符串",
}
export default zh_CN;
主入口文件mian.js
import React, {Component} from 'react';
import { IntlProvider, addLocaleData } from 'react-intl';
function getLocale(lang){
let result={};
switch(lang){
case 'zh_CN':
result = require('./locale/zh-config');
break;
case 'en_US':
result = require('./locale/en-US.config');
break;
default: result = require('./locale/zh-config');
}
return result.default || result;
}
class Lang extends React.Component{
constructor(props){
super(props);
this.state={
lan:" "
}
this.changeLang=this.changeLang.bind(this);
}
changeLang(p){
if(p==='en-US'){
this.setState({
lan:"en_US"
})
}
else {
this.setState(
{lan:"zh_CN"}
)
}
}
render(){
const {lan} = this.state;
const appLocale = getLocale(lan);
addLocaleData(...appLocale.data);
window.lang = appLocale.locale;
return (
this.changeLang('zh-CN')} >中文
this.changeLang('en-US')}>En
)
}
}
ReactDOM.render( ,document.getElementById('root'));
当设置了上述两个文件时,我们可以根据当前locale值来找对应的配置文件,从而找到对应的语言文件,再去匹配对应的id,拿到value值。我们在主入口文件中进行设置,切换到对应语言时,导入该语言的配置文件和语言包,从而全面替换字符串,实现多语言切换。
- bootstraptable的国际化开发
bootstraptable的国际化开发难点在于,他的官方文档这方面写的较简洁,所以摸索了较长时间,最后也是同过injectIntl高阶组件包裹table,从而拿到当前语言,再配置表格自动导入对应的语言包。
import createReactClass from 'create-react-class';
import 'bootstrap';
import 'bootstrap-table';
import {injectIntl } from "react-intl";
import Main from './main/Main';
const Layout = createReactClass({
changeLang (p){
switch(p){
case "en-US": require("bootstrap-table-locale-en");
break;
case "th-TH": require("bootstrap-table-locale-th");
break;
case "zh-CN": require("bootstrap-table-locale-zh");
break;
default: require("bootstrap-table-locale-en");
}
},
render(){
this.changeLang(this.props.intl.locale)
return (
);
},
});
module.exports = injectIntl(Layout);
- antd国际化开发
antd 提供了一个 React 组件 LocaleProvider 用于全局配置国际化文案。
LocaleProvider 使用 React 的 context 特性,只需在应用外围包裹一次即可全局生效。
mian.js中有具体的使用方法,可以参考。
解决store中的多语言实现
项目中调用接口的回调处理都在store文件中实现,会有一些提示信息。但是react-intl并不支持store中字符串的多语言,这个比较不好处理,在git上找到了一种解决方案。
首先添加一个组件 CurrentLocale.js
import {injectIntl} from 'react-intl';
// Does not actually render anything visible.
// We need it to provide access to internationalization for classes
// which are not a React component
class CurrentLocale extends React.Component {
static CurrentLocale = null;
componentWillMount() {
if (!CurrentLocale.instance)
CurrentLocale.instance = this;
}
render() {
return null;
}
}
export default injectIntl(CurrentLocale);
export function intl() {
return CurrentLocale.instance.props.intl;
}
export function formatMessage(...args) {
return intl().formatMessage(...args);
}
在项目入口处引入
然后在store文件中引入使用即可
import {formatMessage} from "../../../../locale/CurrentLocale";
class MyComponent extends Reflux.Store{
onFunctionCompleted(res){
if(res.state==='success'){
message.success(formatMessage({id:"OperateSuccess"}));
}else{
Modal.error({title:formatMessage({id:"OperateFailed"}),content:res.error});
}
}
}
export default MyComponent ;
写在后面
毕业后的第一个项目就要求实现国际化,对于之前没有项目经验又没有人带的我来说,挑战巨大。因为之前没有接触过,所以全部都是边学边用,发现可以参考的文章不是很多,所以总结一下自己开发过程中的难点和通点,如果这篇文章可以帮助到大家,那么也算是做了一件有意义的事情。