国际化

基本思路

国际化是我们在项目中经常遇到的需求,在 React 的项目中实现国际化并不难。比如我们在初始化项目时写的 hello world 的例子:

export default () => {
  return <div>hello world</div>;
}

要实现这个最简单页面的国际化那么其实只要能够将代码中的 hello world 替换为能够按照当前语言环境变化的变量即可:

const lang = window.navigator.language;

export default () => {
  const helloworld = lang === 'en-US' ? 'hello world' : '你好';
  return <div>{helloworld}</div>;
}

按照上面的代码,当 lang 不同时,{helloworld} 对应的实际的内容也会变得不一样。你可以在你的课程代码中尝试一下,你会得到中文的 你好

从这个例子其实可以看到,基于 React 开发的项目要实现国际化其实并不困难。只要你将你项目中原本写死的所有文案都替换成为能够按照语言环境而变化的变量即可。基于 antd 开发的项目国际化可以分为两部分,第一部分是 antd 组件的国际化,比如下图所示的 DatePicker 组件中的中文部分。第二部分是项目中自己开发的业务组件(页面)的国际化。

接下来会对这两部分分别做介绍。

antd 的国际化

antd 中,提供了一个 LocaleProvider 组件。该组件接受一个属性 locale,该属性为当前语言的文案。antd 会通过 react 的 context 将这些信息传递给被 LocaleProvider 包裹的子组件,从而实现国际化。你可以参考下面的示例代码在你的课程代码中做一个实际的尝试:

// 对应课程参考代码中的 src/page/locale.js
import zhCN from 'antd/lib/locale-provider/zh_CN';
import { DatePicker, LocaleProvider } from 'antd';

export default () => {
  return (
    <LocaleProvider locale={zhCN}>
      <DatePicker />
    </LocaleProvider>
  )
}

如上所示,antd 提供了一些列的内置的语言资源,他们的位置在 antd/lib/locale-provider/*。我们直接使用它们作为 LocaleProvider 的 locale 属性。

LocaleProvider 组件中定义了 getChildContext,它会将相关的国际化语言信息挂载到 context.antLocale 中,这样在它的子组件中被包含的 antd 组件就能够通过 context 获取到对应的国际化数据了。如果你对 antd 国际化的内部的细节感兴趣可以查看 antd 的源代码,在这里不做过多介绍。

注:antd 内部使用的 context 特性是原有的 React 版本的特性,在未来将会迁移到新的 API,不过这对于 antd 的使用者来说是不需要关心的。

业务组件的国际化

除了组件的国际化以外,你可能还需要将你自己书写的业务组件进行国际化。我们推荐使用 react-intl 来作为国际化方案。

你首先需要使用下面的命令来安装 react-intl

cnpm install react-intl --save

react-intl 的国际化方案和 antd 其实是类似的,它提供了一个 IntlProvider 组件,你可以把你的业务代码包装在这个组件中。然后就可以通过它提供的方法,在自己的业务组件里便利地实现国际化。我们继续基于上面创建的代码添加相关内容,如下面代码所示:

// 对应课程参考代码中的 src/page/locale.js
import zhCN from 'antd/lib/locale-provider/zh_CN';
import { DatePicker, LocaleProvider } from 'antd';
import {
  FormattedMessage,
  IntlProvider,
  addLocaleData,
} from 'react-intl';
import zhData from 'react-intl/locale-data/zh';

const messages = {
  'helloworld': '你好',
};

addLocaleData(zhData);

export default () => {
  return (
    <IntlProvider locale="zh" messages={messages}>
      <LocaleProvider locale={zhCN}>
        <div>
          <DatePicker />
          <FormattedMessage id="helloworld" />
        </div>
      </LocaleProvider>
    </IntlProvider>
  )
}

通过 FormattedMessage 组件,react-intl 会按照你传给这个组件的 id 找到当前对应语言的实际内容。当然在上面的例子中,页面最终会显示你好。你可以通过修改 IntlProvider 的属性 messages 的内容来切换页面中要显示的语言。

除此之外还有一个 locale 属性,和 addLocaleData 方法。它们是用于指定 react-intl 中的一些非特殊场景的国际化资源的,比如 react-intl 提供的 FormattedTime。关于 react-intl 的更多 API 你可以参考它的文档,这里不做过多介绍。上面介绍的内容已经足以满足项目大部分场景的需求,但是你是不是觉得还是有一些复杂,或者说是麻烦。那么接下来我们会换一种更简洁的方式来做国际化,umi 提供了更方便的方案,当然umi 提供的方案也是基于上面所介绍的内容来支持的。

在 umi 中使用

在 umi 中,为了方便开发者更方便的使用,对 react-intlantd 进行了封装。你可以按照如下的方法更快的实现应用的国际化。

初始化配置

在 umi 中,你可以通过在插件集 umi-plugin-react 中配置 locale 打开 umi-plugin-locale 插件。

export default {
  plugins: [
    ['umi-plugin-react', {
      antd: true,
      dva: true,
      locale: {
        enable: true,
      },
    }],
  ],
  // ...
}

之后 umi 会帮你在自动的通过 LocaleProvider 将 antd 的组件国际化。它会按照当前浏览器的语言自动切换语言,当然你也可以通过配置关闭自动选择语言的功能,通过它提供的 API 设置语言。另外,它约定了 locale 目录作为业务相关资源的存放路径。你可以简单的就能实现业务组件的国际化。

具体 locale 的配置详情和 API 参考相关文档

业务组件国际化

以 hello world 为例子,添加上一步的配置后,我们在 src 目录下新建 locale 文件夹,并新建 zh-CN.jsen-US 两个文件。内容分别如下:

// src/locale/zh-CN.js
export default {
  helloworld: '你好',
}
// src/locale/en-US.js
export default {
  helloworld: 'hello world',
}

然后修改 HelloWorld.js 为:

import {
  FormattedMessage,
} from 'umi/locale';
// src/page/HelloWorld.js
export default () => {
  return <div><FormattedMessage id="helloworld" /></div>;
}

某些情况下你可能需要在 JS 逻辑中拿到相关文案而不是在 JSX 中通过 FormattedMessage 组件使用,那么你可以使用 formatMessage

import {
  formatMessage,
} from 'umi/locale';

console.log(formatMessage(
  {
    id: 'helloworld',
  },
));

切换语言

我们通过 IntlProviderLocaleProvider 的属性(locale 和 messages)可以指定当前应用的语言,同样的通过修改我们传递给它们的属性来实现语言的切换。同时为了更好的用户体验,我们应该保存用户当前选择的语言,以至于用户刷新页面之后不会丢失之前的选择。你可以通过 localStorage 来实现。当用户点击语言切换的按钮(或者下拉菜单)后,我们将用户选择的语言保存到 localStorage 中,同时切换语言。

在教程对应的代码的 layouts/GlobalHeader.js 文件中我们添加了一段代码:

<Button
  size="small"
  onClick={() => {
    this.changLang();
  }}
>
  <FormattedMessage id="lang" />
</Button>

通过这段代码我们在应用的顶部导航栏添加了一个按钮,点击这个按钮则会调用 changLang 切换语言,changLang 的代码如下:

changLang() {
  const locale = getLocale();
  if (!locale || locale === 'zh-CN') {
    setLocale('en-US');
  } else {
    setLocale('zh-CN');
  }
}

getLocalesetLocaleumi/locale 中提供的方法。如果你没有用 umi 你也可以自己通过 localStorage 来实现。在 setLocale 中我们把语言信息保存到了 localStorage 中,然后刷新页面。刷新页面后应用会重新初始化,页面会重新渲染。然后我们就可以在应用启动时通过 getLocale 拿到用户选择的语言并设置到 IntlProviderLocaleProvider 上面。当然,如果你使用了 umi,你只需要调用 setLocale 即可,其它的操作它会帮你完成。

当然,如果要追求更好的用户体验 setLocale 后不应该刷新页面就可以做到语言的实时切换,这部分内容我们会在后面补充完善。