1

今天来介绍一个非常international的东西。

i18n
国际化(internationalization)的简称。之所以叫i18n,是因为字母i和n之间有18个字母,所以才叫i18n。不要认为这是一个高大上的名词,其实就是因为懒才简写的。hiahiahia...

因为本系列是以React为中心,所以只介绍React项目中的国际化解决方案。当然还有很多很多...很多别的国际化解决方案,但是不是所有的轮子都适合React这辆开往幼儿园的车。
实际上国际化在日常项目中用的没那么频繁,除非有业务需求,比如要做一个非常international的项目。目前在React中比较热门的两个包就是react-intl-universalreact-intl。因为本文重点介绍对象是前者,所以我们先简单介绍下后者。当然在这里不会把它的使用方法列出来,而是把它的缺点列出来,为什么呢?因为笔者懒啊!
react-intl不足的地方主要是两个:

它只能用于视图层。举个例子,比如React.Comoponent对象,但是对于Vanilla JS就会显得很无力了,因为它无法在Vanilla JS中实例化。(这里会有人感到奇怪,Vanilla JS是什么鬼?哈哈...百gu度ge吧,不会发现新大陆)!

其次

想使用国际化方法,我们必须要利用它的一个方法将自己的组件转化成另外一个class。这就比较蛋疼了,例子如下:
import { injectIntl } from 'react-intl';
class MyComponent extends Component {
  render() {
    const intl = this.props;
    const title = intl.formatMessage({ id: 'title' });
    return (<div>{title}</div>);
  }
};
export default injectIntl(MyComponent);

看我笔者第一篇文章的朋友应该有印象:所有被包裹过的组件,如果你想获得原本的组件的对象,那得调用相应的方法。这里也不例外,如果我们想获取组件的原对象,那就得这么做:

class MyComponent {...}
export default injectIntl(MyComponent, {withRef: true});
 
class App {
  render() {
    <MyComponent ref="my"/>
  }
  getMyInstance() {
    console.log('getMyInstance', this.refs.my.getWrappedInstance());
  }
}

这样写会不会觉得太麻烦了...
所以Alibaba前端组就按捺不住了,然后就搞出了自己的react-intl-universal。看名字不就是在react-intl后面加个universal吗?的确是这样,不过笔者不清楚这个框架的核心逻辑是不是参考的react-intl,但是单从名字来看就有点"可疑"了,翻译就是react-intl的通用版(当然,纯属意淫,一笑而过!)。

react-intl-universal

作为一个国际化解决方案,首先实现国际化是它的基本功能。其次它还有一些别的功能,比如文本格式化、货币格式化、时间格式化等等,我相信这些都是我们页面开发经常使用到的功能。

i18n

首先来看一下它的技术功能:国际化
react-intl-universal采用了与组件无关的方法来实现国际化。国际化的本质其实就是将我们预先设置好的不同语言的句子按照语言环境显示在页面上。

import intl from 'react-intl-universal';

通过intl这个对象来实现初始化和国际化处理。我们可以认为这个intl是一个单例对象。我们在App启动的时候对其进行初始化,尔后在别的地方再次导入的时候仍然是一个已经初始化过的对象。在这种情况下,国际化处理就会变得异常简单。其次就是准备多语言句子了,传统的在前端处理这个问题是将不同语言的句子放在不同的json文件中再导出,文件结构如下:
clipboard.png
这样我们就可以在App启动或者切换语言的时候导入相应的json对象了。

首先是API介绍

intl对象主要有三个常用的用于国际化处理的API,determineLocale、init、get

  • determineLocale
    看到方法名就应该知道它是用来干什么了。它用来确定在整个体系中使用的是哪种语言。看代码:
let currentLocale = intl.determineLocale({
    urlLocaleKey: "lang",
    cookieLocaleKey: "lang"
});

react-intl-universal确定语言的方式有三种,一个是通过urlLocaleKey,即lang关键字从url中获取是哪种语言。比如:http://localhost?lang=en-US,因为lang对应的值是en_US,所以语言为英文。其次是从Cookie获取,因为Cookie也是以键值对形式存储的,所以会检查当前域下的Cookie是否有对应的lang。如果上述两种都没有,那么会默认使用浏览器当前的语言类型。当然上述的urlLocaleKey和cookieLocaleKey是可以自定义的,不是固定的lang.

  • init

init方法即用来初始化intl对象。初始化参数主要是两个,一个是currentLocale即当前的语言,另一个是locales即当前语言对应的json对象,比如{"en-US":{"key1":"value1"} 或者 {"zh-CN":{"key1":"值1"}}

  • get
    get方法就相对简单,就是根据键去intl中获取对应的值,这里不做过多解释。

完整的初始化过程如下:

class App extends Component{
    ....
    
    componentDidMount() {
        this.loadLocales();
    }

    loadLocales() {
        const _self = this;
        let currentLocale = intl.determineLocale({  //如果cookie和url中均没有相关参数,那么以浏览器语言为准
            urlLocaleKey: "lang",
            cookieLocaleKey: "lang"
        });

        http
            .get(`locales/${currentLocale}.json`)   //理解为按需加载并且locales文件夹需要放在public文件下供http访问
            .then(res => {
                return intl.init({
                    currentLocale,
                    locales: {
                        [currentLocale]: res.data //如果key是变量,那么需要用[]包一下
                    }
                });
            })
            .then(() => {
                _self.setState({initDone: true});
            });
    }
    
    ....
}

然后在需要国际化的地方这么使用

import intl from 'react-intl-universal';
<p>{intl.get('name')}</p>

是不是很简单? 而且完全避免了react-intl的两个缺点。

格式化工具

前面说了react-intl-universal不仅仅可以用来做国际化处理,还可以用来做简单的文本格式化处理。下面我们列举几个常用的。

Html Snippet

假如我们的json文件中有这么一段

...
"red": "<p style='color:red'>红色</p>",
...

如果我们直接用get方法获取的话,那么会直接把<p style='color:red'>红色</p>给打印出来。如果我们想将它以html片段的形式打印出来的话,就使用getHTML方法,它在获取到句子的时候会进行解析并生成最终的Html Snippet。

Default Message

缺省值其实就是默认值,是对于json键值对的默认值。将入我们去获取一个json中没有的键值对那么系统就会报错。如何去规避这个问题呢?react-intl-universal给我们提供了这样一个方法:

intl.get('not-exist-key').defaultMessage('default message')

这是一个链式调用。如果json中没有not-exist-key这个键,那就会默认返回defaultMessage的参数。简写是intl.get('not-exist-key').d('default message')

Message With Variables

假如某个句子包含了一个变量怎么办?比如一个用户名,我们只有在用户登录的时候才知道他的用户名。

{
    "me": "你好,我是{me}"
}

此时就用到了get放的第二个参数。对于上面的例子,我们可以这样处理:

<p>{intl.get('me', {'me': '皮卡丘'})}</p>

get方法会找出句子中被{}包住的变量me,然后在第二个参数(json对象)找出me对应的值皮卡丘并将{me}整个用皮卡丘替换。另外需要注意的是,json对象只能为一层,不可嵌套

Display Currency

它还可以用来格式化货币。假如有这么一段句子

{
  "price": "这件衣服是 {price,number,CNY} 人民币",
}

如果我们想将一个数字以人民币的形式写进去的话可以这么做:

{intl.get('price', {'price': 1000})}

最终显示结果是:这件衣服是 ¥1,000 人民币
其实它做了两件事:一个是加符号,另一个是加分隔符。同时CNY表示人民币,USD表示美元

Display Dates

然后是日期的处理。假如有这么一段话:

{
    "date": "今天是{date,date,full}"
}

然后我们这么使用它的话:

<p>{intl.get('date',{'date':new Date()})}</p>

显示结果是今天是2018年12月3日星期一。其实{date,date,full}这段指令就是将date变量替换成对应日期(new Date())并以long形式展示。

同时日期展示形式有四种

  • short: shows date as shortest as possible
  • medium: shows short textual representation of the month
  • long: shows long textual representation of the month
  • full: shows dates with the most detail

他们之间有什么不同呢?我们用刚刚的例子做个展示:

  • short: 今天是18/12/3
  • medium: 今天是2018年12月3日
  • long: 今天是2018年12月3日
  • full: 今天是2018年12月3日星期一

Display Times

最后是时间。我们按部就班来。假如有这么一段话:

{
    "time": "现在时间是{time,time,short}"
}

然后我们这么使用它的话:

 <p>{intl.get('time',{'time':new Date()})}</p>

显示结果是现在时间是下午5:54。其实{time,time,short}这段指令就是将time变量替换成对应日期(new Date())并以short形式展示。

但是时间展示形式只有三种,它没有full

  • short: shows date as shortest as possible
  • medium: shows short textual representation of the month
  • long: shows long textual representation of the month

他们之间有什么不同呢?我们用刚刚的例子做个展示:

  • short: 现在时间是下午5:54
  • medium: 现在时间是下午5:58:22
  • long: 现在时间是GMT+8 下午5:58:50

上述贴出来的示例都是在中文环境下。如果有兴趣的朋友可以把整个例子download下来本地运行下,边看边写,受益匪浅。好了,收拾下班咯...


风吹过的夏夜
295 声望31 粉丝

前端工程师