随着国际化发展,多语言的需求愈来愈常见,单一的语言已经远不能知足需求了。做为一个组件库,支持多语言也是基本能力。 多语言功能的本质实际上是文本的替换,一个词汇“OK”,在英文语境下是“OK”,日语语境下是“確認”,中文语境下多是“肯定”也多是“确认”“好的”等等。html
本文将以简单组件为切入点,向你们展现Fusion组件库是如何支持多语言能力的。react
咱们以一个常见的组件Search举例,用户输入内容后可经过点击“搜索”、“清除”按钮触发相应的事件,简化代码后以下:git
class Search extends React.Component {
render() {
return (
<div> <input /> <button>搜索</button> <button>清除</button> </div>
);
}
}
export default Search;
复制代码
多语言处理最简单直接的办法是直接替换文本,不一样语言环境下可能要将“搜索”替换为“search”、“サーチ”,将“清除”替换为“clear”、"クリア"等。同时做为一个组件库,涉及到的大可能是简单词汇而不是句子,所以咱们首选配置化的方式:github
// 抽取语言包
// search-en-us.js
{
search: 'search',
clear: 'clear'
}
// search-zh-cn.js
{
search: '搜索',
clear: '清除'
}
复制代码
import searchZhCN from 'search-zh-cn';
class Search extends React.Component {
static propTypes = {
locale: PropTypes.object
};
static defaultProps = {
locale: searchZhCN
};
render() {
return (
<div> <input /> <button>{locale.search}</button> <button>{locale.clear}</button> </div>
);
}
}
export default Search;
复制代码
这样就实现了单个组件Search的多语言支持。web
可是,为每一个组件对应一个语言包文件的作法显然很不方便。Fusion Next做为一个PC端的React组件库有50+组件,内置词汇70多条,目前有13个组件须要国际化语言能力。ide
以语种为单位,将同一种语言下的映射关系放到一个文件里进行处理的方式更为高效。函数
为便于维护管理,加强可拓展性,咱们以语种为单位抽取语言包。将同一语种下全部组件的语言对象{key: '文案'}
放到一块儿,以displayName做为key,语言对象做为value,调整语言包以下:ui
// 抽取语言包
// zh-cn.js
{
Search: {
search: '搜索',
clear: '清除'
},
Dialog: {},
...
}
复制代码
这也是Fusion如今语言包的结构 unpkg.com/@alifd/next… 因为语言包结构的调整,须要同时修改Search组件语言对象的默认值:this
import zhCN from 'zh-cn';
class Search extends React.Component {
...
static defaultProps = {
locale: zhCN.Search
}
...
}
export default Search;
复制代码
在使用时,用户将语言包对象以props参数的形式传给组件便可直接改变文案:spa
import jaJP from 'xxxx/ja-jp.js';
<Search locale={jaJP.Search}>
<Dialog locale={jaJP.Dialog}>
复制代码
然而,在web应用愈来愈复杂的如今,不少项目里里可能会用到几十甚至上百个组件,这样给每一个组件手动传locale参数的方式一方面比较蠢,另外一方面开发者须要关心locale参数,在语言切换时改变值的内容。
而且语言的设置大都是以项目(或者页面)为单位的,有没有更聪明、对开发者更友好的使用方式呢?
import zhCN from 'zh-cn';
<ConfigProvider locale={zhCN}> <Search /> <Dialog /> </ConfigProvider>
复制代码
使用者在使用时基础组件时不用关心locale的变化,子组件们共享了<ConfigProvider>
组件上传入的语言配置,更改这一配置能够一键设置子组件的语言包。如何实现的这一功能呢?
React中,若是不想经过逐层传递props或者state的方式来传递数据,不如考虑考虑Context。
借助Context能够实现跨层级的组件数据传递。
它的使用场景是生产者消费者模式,在上面的例子中,<ConfigProvider>
就是生产者,<Search> <Dialog>
组件就是消费者。 他们分别经过一系列属性方法(childContextTypes属性 getChildContext方法
/contextTypes属性
),创建起一条通讯线。
// 生产者
class ConfigProvider extends React.Component {
// 声明Context对象
static childContextTypes = {
nextLocale: PropTypes.object
}
// 返回Context对象
getChildContext () {
return {
nextLocale: {}
}
}
render () {
return this.props.children;
}
}
复制代码
// 消费者
import zhCN from 'zh-cn';
class Search extends React.Component {
static propTypes = {
locale: PropTypes.object
};
static defaultProps = {
locale: zhCN.Search
};
// 声明须要使用的Context属性
static contextTypes = {
nextLocale: PropTypes.object
};
render() {
const locale = Object.assign({}, nextLocale['Search'], locale);
return (
<div> <input /> <button>{locale.search}</button> <button>{locale.clear}</button> </div>
);
}
}
export default Search;
复制代码
这样,直接给<ConfigProvider>
传递国际化参数,就能够改变其子组件所使用的语言包。
数据传递的问题解决了,按照这个思路对组件进行改造就能够完美支持一键切换语言了~ 事实上,这个解决方案通用性很强,只要子组件(包括自定义组件)都按上面的方式进行改造,就能够支持语言包的切换。
但同时咱们也发现,改造工做高度重复,都是新增contextTypes静态属性、对props和context上的locale进行merge。有没有对开发者(基础组件开发者、业务组件开发者)更友好的方式来下降这部分重复性工做呢?
Fusion为Util类组件ConfigProvider增长了一个静态方法ConfigProvider.config(Component)
,在这个函数里进行对于locale的改造工做,它返回一个新的受控制的高阶组件(HOC)NewComponent。
NewComponent 至关于被 ConfigProvider 代理了一层。
在ConfigProvider.config()这个函数里
这样,只要子组件通过该函数处理,就可让ConfigProvider
“遥控”语言包切换
import zhCN from 'zh-cn';
class Search extends React.Component {
static propTypes = {
locale: PropTypes.object
};
static defaultProps = {
locale: zhCN.Search
};
render() {
return (
<div> <input /> <button>{locale.search}</button> <button>{locale.clear}</button> </div>
);
}
}
// 通过统一处理
export default ConfigProvider.config(Search);
复制代码
ConfigProvider.config(Component)的语言包文案分发处理逻辑简化以下:
// ConfigProvider.jsx
function config(Component) {
class ConfigedComponent extends React.Component {
static propTypes = {
...(Component.propTypes || {}),
locale: PropTypes.object,
};
static contextTypes = {
...(Component.contextTypes || {}),
nextLocale: PropTypes.object,
};
render() {
// 组件props上直接设置
const { locale } = this.props;
// ConfigProvider"遥控"设置
const { nextLocale = {} } = this.context;
// 组件上直接设置语言包,优先级高于在父组件ConfigProvider上设置。
const newLocale = Object.assign({},
nextLocale[Component.displayName],
locale
);
return (
<Component locale={newLocale}/> ); } } return ConfigedComponent; } 复制代码
这样就基本完成了组件库的多语言能力建设,这也是Fusion Next组件库的多语言支持的思路。
除此以外,ConfigProvider还有内置了其余通用能力,例如组件的镜像反转RTL,pure render开关、修改样式的默认前缀等,更多能够查看 ConfigProvider源代码 和 使用文档 了解。