React项目国际化(antd)多语言开发

15,220 阅读9分钟

前言

最近搭建一个项目,从0开始做,需要做多语言的国际化,今天搞了一下,基本达到了想要的效果,第一次发掘金勿喷,在这里简单分享下:

背景

国际化方案

  • 国际化方案概述
  • 前端国际化详解、举例
  • 国际化资源文件管理
  • 项目之间、开发者与翻译者之间的协作
  • 国际化规范附录
  • 扩展阅读

国际化方案概述

国际化是一个看似简单,实则非常复杂的领域,实际进行国际化工作时,大家会发现它往往会涉及很多内容:

  • 前端国际化
  • 服务端国际化
  • 国际化资源文件管理
  • 项目之间、开发者与翻译者之间如何协作

而且,国际化方案往往与具体的技术栈是绑定的。

本国际化方案仅针对 React 技术栈,且不会涉及服务端国际化内容。

前端国际化详解、举例

国际化的核心步骤有两步:

  1. 创建资源文件,以 key-value 方式存储
  2. 加载资源文件,将页面上 key 的内容替换为相关 value

一些探索

也说不上是探索吧,就Google了一波, GitHub 上找了一个比较成熟的库如下:
react-i18next

react-intl

react-intl-universa

接下来一一介绍一下如何使用

react-i18next

安装

npm install i18next react-i18next --save

引入App.js

import i18n from 'i18next';
import { useTranslation, initReactI18next } from 'react-i18next';

初始化

const lng = 'en';
i18n
  .use(initReactI18next) // passes i18n down to react-i18next
  .init({
    resources: {
      en: {
        translation: {
          Welcome: 'Welcome to React',
        },
      },
      zh: {
        translation: {
          Welcome: '欢迎进入react',
        },
      },
    },
    lng: lng,
    fallbackLng: lng,

    interpolation: {
      escapeValue: false,
    },
  });

实际使用结果

function App() {
  const { t } = useTranslation();
  return (
    <div className="App">
      <p>{t('Welcome')}</p>
    </div>
  );
}

export default App;

封装后的成果:

// ...
import i18n from '@src/i18n';
// xxx component
console.log('i18n来一发:', i18n.t('INVALID_ORDER'));
render() { 
  // ...
  <button> {i18n.t('INVALID_ORDER')} </button>
}

react-intl

背景:

用于国际化 React 组件,提供 React 组件和 API 来格式化日期,数字,字符串(包括单复数和翻译)。

react-intl库是业界很受欢迎的库之一。 react-intl用包裹组件的形式装饰你的React.Component,动态注入国际化消息,以便能够动态加载语言环境数据,并且无需重新加载页面

安装:

 npm install react-intl --save

载入语言环境数据。

React Intl 依赖这些数据来支持单复数和相对时间格式化的功能。

// Main.js
import { addLocaleData } from 'react-intl'; /* react-intl imports */
import en from 'react-intl/locale-data/en';
import zh from 'react-intl/locale-data/zh';
addLocaleData([...en, ...zh]);  // 引入多语言环境的数据  

虽然我只用到了文本翻译的功能,以为就不需要加载这些数据,但后来发现这是必须的步骤。不然会报错:

[React Intl] Missing locale data for locale: "zh". Using default locale: "en" as fallback.

使用

组件包裹需要实现国际化的根组件,这个组件树之后就会在配置的i18n上下文中了。

由于项目中用到了react-hot-loader,根组件 Main<AppContainer>包裹了,并且是从单独的一个文件 import 了 Main 组件。

//app.js
import { AppContainer } from 'react-hot-loader'
import Main from './components/Main'
//... ...
const render = Component => {
    ReactDOM.render(
        <AppContainer>
            <Component />
        </AppContainer>,
        document.getElementById('app')
    )
}
render(Main);

于是直接在 Main.js 中使用<IntlProvider>组件。把它加到 render()返回节点的最外层就行了。

// Main.js
import { addLocaleData, IntlProvider } from 'react-intl'; /* react-intl imports */
render(){
    return (
        <IntlProvider>
          //··· ···
        </IntlProvider>
    )
}

添加多种语言对应的文本。

比如要支持中英文,为了方便之后维护,可以新建两个文件:

locale目录下新建
// en_US.js
const en_US = {
    hello: "Hello!",
    //... ...
}
export default en_US;

// zh_CN.js
const zh_CN = {
    hello: "你好!",
    //... ...
}
export default zh_CN;

然后在Main.js中引入这两个变量。

// Main.js
import zh_CN from "../locale/zh_CN"     // import defined messages in Chinese
import en_US from "../locale/en_US"     // import defined messages in English

全局配置当前的语言,和相对应的文本。

即配置<IntlProvider>组件的两个属性localemessages

// Main.js
render(){
    let messages = {}
    messages['en'] = en_US;
    messages['zh'] = zh_CN;
    return (
        <IntlProvider locale={this.state.lang} messages={messages[this.state.lang]}>
            //··· ···
        </IntlProvider>
    )
}

这样基本配置就完成了,可以通过改变 this.state.lang的值来改变页面语言。

// Main.js
/**
 * Change language
 * @param {String} lang new language
 */
changeLanguage(lang) {
    this.setState({
        lang: lang
    })
}

接下来,添加翻译的文本到页面中

基本只需要使用到一个组件:<FormattedMessage>。这个组件默认生成一个<span>,内容是翻译后的文本,也就是 messages中对应字段的值。

在需要添加国际化文本的组件中,引入FormattedMessage组件。

import { FormattedMessage  } from 'react-intl'; /* react-intl imports */
//... ...
<FormattedMessage id="hello" />

当前语言为en时,生成结果:

<span>Hello!</span>

到这里,react-intl基本的国际化就实现了。

注意:目前react-intl也已经支持hooks写法,新版的配置项相对简单已经抛弃了addLocaleData,可以通过useIntl直接拿到locale, messages, formatMessage,更详细大家可以查看官方文档demo地址

尾声

编写规范

  1. 必须填写 defaultMessage,并将 defaultMessage 作为中文翻译
  2. id 不得重复
  3. 在使用 intl.formatMessage() 时,必须使用 defineMessages,预定义消息

扩展阅读

react-intl-universal

背景:

由阿里巴巴推出的react国际化库

这个库最好地方在于使用简单方便,侵入性低

安装

使用npm安装

npm install react-intl-universal --save

初始化

在初始页面,进行该库的初始化,配置语言包,json文件根据需要支持几种语言来决定,下面的图片中仅支持中英文

image.png

于项目入口文件中配置国际化

import intl from 'react-intl-universal';

// locale data
const locales = {
  "en-US": require('./locales/en-US.json'),
  "zh-CN": require('./locales/zh-CN.json'),
};

class App extends Component {

  state = {initDone: false}

  componentDidMount() {
    this.loadLocales();
  }

  loadLocales() {
    // react-intl-universal 是单例模式, 只应该实例化一次
    intl.init({
      currentLocale: 'en-US', // TODO: determine locale here
      locales,
    })
    .then(() => {
    this.setState({initDone: true});
    });
  }

  render() {
    return (
      this.state.initDone &&
      <div>
        {intl.get('SIMPLE')}
      </div>
    );
  }

}

语言配置文件可以是json或者js,json格式如下:

英语配置文件 ./locales/en-US.json

{
    "SIMPLE": "Simple Sentence",
    "LANG_TYPE": "paas-us",
    "INPUT_MOBILE": "Mobile Number",
    "HELLO": "Hello, {name}. Welcome to {where}!"
}

中文配置文件 ./locales/zh-CN.json

{
    "SIMPLE": "简单的句子",
    "LANG_TYPE": "paas-cn",
    "INPUT_MOBILE": "手机号",
    "HELLO": "你好, {name}。欢迎来到{where}!"
}

调用

在刚才的初始化代码中,render函数里面已经进行了调用了。在整个项目的其他地方,由于已经进行了初始化了,所以可以直接调用了。调用的例子如下:

import intl from 'react-intl-universal';

class Test extends Component {
  render() {
    return (
      <div>
        {intl.get('INPUT_MOBILE')}
      </div>
    );
  }

}

切换

再来看一下初始化函数

intl.init({
    currentLocale: 'en-US', // TODO: determine locale here
    locales,
})

初始化的时候,除了直接指定语言外,还可以由函数determineLocale根据以下配置进行指定:

  1. Url中的query参数
  2. cookie中的参数
  3. 浏览器的当前语言(当没有配置query参数和cookie参数时)

这些配置的生效如下面代码所示:

let currentLocale = intl.determineLocale({
  urlLocaleKey: "lang",
  cookieLocaleKey: "lang"
});

intl.init({
    currentLocale, // determine locale here
    locales,
})

那么,我们可以利用如下方式进行切换:当选择相应语言时,触发回调函数,在函数内,修改url或者cookie,然后进行页面刷新,重新初始化,即可以切换语言了。

下面我给出一个根据cookie切换的例子:

handleClick = (lang) => {
    Cookies.set('lang', lang, { expires: '1Y' });
    window.location.reload(true);
}

进阶

react-intl-universal库在语言处理上,还有很多其他功能,如:

  • 带HTML标签的HTML 文本
  • 变量
  • 单复数形式
  • 货币
  • 日期

html中引用资源包里的文字

a.纯文字,使用intl.get()

<div> {intl.get('SIMPLE')} </div>

b.带html模板的文字,使用intl.getHTML()方法

例如资源包里是这样定义的

{ 
   "SIMPLE": "This is <span style='color:red'>HTML</span>" 
}

引用时需使用getHTML()方法获取文字

<div>{intl.getHTML('SIMPLE')}</div>

数字形式和千分位分隔符

下例中的变量为num,给它标记为plural后,它的值只能为数字。当num值为0时,显示"no photos.";当值为1时,显示"one photo.";当值为其他数字比如25000时,显示“25,000 photos.”,这里的'#'表示给num的值添加千分位分隔符后显示

{ 
   "PHOTO": "You have {num, plural, =0 {no photos.} =1 {one photo.} other {# photos.}}" 
}

引用结果如下:

intl.get('PHOTO', {num:0}); // "You have no photos."
intl.get('PHOTO', {num:1}); // "You have one photo."
intl.get('PHOTO', {num:1000000}); // "You have 1,000,000 photos."

显示货币格式

具体语法为{变量名, 类型, 格式化},下例中变量名为"price",它的类型是number,"USD"表示在值前面加上美元符号($)

{ 
   "SALE_PRICE": "The price is {price, number, USD}" 
}

引用及显示结果如下:

intl.get('SALE_PRICE', {price:123456.78}); // The price is $123,456.78

显示日期

语法同上:{变量名, 类型, 格式化},当类型为"date"时,格式化有以下几个选项:short,medium,long,full,也可不格式化

{
  "SALE_START": "Sale begins {start, date}",
  "SALE_END": "Sale ends {end, date, long}"
}

引用及显示:

intl.get('SALE_START', {start:new Date()}); // Sale begins 4/19/2017
intl.get('SALE_END', {end:new Date()}); // Sale ends April 19, 2017

配置默认message

当遇到比如因拼写错误导致无法匹配到资源包里的文字时,可以事先配置默认的message,这时当无法匹配的资源包时会显示默认message

//"defaultMessage()"可简写为"d()"
intl.get('not-exist-key').defaultMessage('没有找到这句话');

同理亦可配置带html模板的默认message

intl.getHTML('not-exist-key').d(<h2>没有找到这句话</h2>)

带变量的message

资源包里的配置如下

{
    "HELLO": "Hello, {name}. Welcome to {where}!" 
}

在html中引用时

<div> intl.get('HELLO', {name:'banana', where:'China'}) </div>

显示的结果为:Hello, banana. Welcome to China!

尾声

到此react-intl-universal基本的使用方法介绍完毕了,如果以上达不到你的需求请前往git翻看更多readme文档和api文档。

git地址:github.com/alibaba/rea…

antd/antd-mobile 国际化方案

LocaleProvider国际化

组件 LocaleProvider 用于全局配置国际化文案

ant.design/components/…

为组件内建文案提供统一的国际化支持

使用
LocaleProvider 使用 React 的 context 特性,只需在应用外围包裹一次即可全局生效。

import { LocaleProvider } from 'antd';
import zh_CN from 'antd/lib/locale-provider/zh_CN';
import moment from 'moment';
import 'moment/locale/zh-cn';

moment.locale('zh-cn');
...

return <LocaleProvider locale={zh_CN}><App /></LocaleProvider>;

提供了英语,中文,俄语,法语,德语等多种语言支持,所有语言包可以在 这里 找到。

注意:如果你需要使用 UMD 版的 dist 文件,应该引入 antd/dist/antd-with-locales.js,同时引入 moment 对应的 locale,然后按以下方式使用:

const { LocaleProvider, locales } = window.antd;

...

return <LocaleProvider locale={locales.en_US}><App /></LocaleProvider>;

到此今天的国际化分享大全就结束了,我个人感觉还是react-intl-universal比较好用,如果对你有帮助记得点点关注哦,

案例已上传到github,有相关需求的可以去看看,第一次写勿喷,如果有问题请指正!