前端国际化方案选择

国际化基础知识

  1. 国际化与本地化

国际化与本地化,或者说全球化,其目的是让你的站点支持多个国家和区域。其中国际化是指功能和代码设计能处理多种语言和文化习俗,能够在创建不同语言版本时,不需要重新设计源程序代码。国际化的英文单词是 Internationalization ,简称 I18N。 本地化是将站点按照特定国家、地区或语言市场的需要进行加工,使之满足特定用户对语言和文化的特殊要求。本地化的英文是 Localization,缩写为 L10N。以 moment.js 为例,其支持 setLocale 方法切换语言相当于国际化,每一个 locale 的配置文件定义了具体区域时间格式相当于本地化。

  1. CLDR

Unicode Common Locale Data Repository (CLDR) 是软件国际化的基石,它作为一个国际标准提供了构建国际化软件所需要的定义和数据。可参考其 json 形式示例 形式

  1. ECMAScript Internationalization API

ECMA 提供的国际化 API(底层使用了 CLDR),包括

  • Intl.Collator, 排序
  • Intl.NumberFormat, 数字的格式化
  • Intl.DateTimeFormat, 日期的格式化

由于兼容性原因,你还需要 polyfill

  1. ICU

一套定义国际化语法的标准,仅仅是一种规范,不同语言有不同的语法,不同语法需要对应一种相应的解析方法,例如 javascript 里的一种 icu 语法,和其解析器:

https://github.com/messageformat/messageformathttps://github.com/messageformat/parser

  1. Format.js

FormatJS is a modular collection of JavaScript libraries for internationalization that are focused on formatting numbers, dates, and strings for displaying to people. It includes a set of core libraries that build on the JavaScript Intl built-ins and industry-wide i18n standards, plus a set of integrations for common template and component libraries.

format.js 是一套国际化的方案(实现了上面的 3 和 4 点),包括以下一些核心库:

核心库

  1. po、pot、mo 三种翻译文件格式
  • po(Portable Object)文件,是面向翻译人员的、提取于源代码的一种资源文件。当软件升级的时候,通过使用 gettext 软件包处理 po 文件,可以在一定程度上使翻译成果得以继承,减轻翻译人员的负担。文件格式示例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#: ./src/js/components/PaymentManagement/OrderFilter/index.js:69
#: ./src/js/components/PaymentManagement/OrderFilter/index.js:67
msgid "请填写表 ID"
msgstr "Please fill in the table ID"

#: ./src/js/utils/helper.js:382
msgctxt "数字金额"
msgid "{count} 千万"
msgstr "{count}0 million"

#: ./src/js/components/Dashboard/StatisticsPanel/API/index.js:33
msgid "{count} 次"
msgid_plural "{count} 次"
msgstr[0] "{count} time"
msgstr[1] "{count} times"
  • pot 文件,性质和 po 一样,只不过它主要用作模板

  • mo( Machine Object)文件是面向计算机的、由 PO 文件通过 gettext 软件包编译而成的二进制文件。程序通过读取 MO 文件使自身的界面转换成用户使用的语言。|

国际化方案

方案一:react-intl

react-intl 是基于 react 整合了 format.js 的 一套 react 国际化方案。

This library provides React components and an API to format dates, numbers, and strings, including pluralization and handling translations.

  1. react-intl 提供 API 和组件两种形式方式使用:

  2. 加载 localeData

1
2
3
import {addLocaleData} from 'react-intl';
import frLocaleData from 'react-intl/locale-data/fr';
addLocaleData(frLocaleData);

The locale data added with this function supports plural and relative-time formatting features as described in Loading Locale Data.

  1. Creating an I18n Context
1
2
3
4
5
6
7
8
9
ReactDOM.render( 
<IntlProvider
locale={usersLocale}
messages={translationsForUsersLocale}
>
<App/>
</IntlProvider>,
document.getElementById('container')
);

根据 locale 加载不同的 messages 文件,messages 格式如下:

1
2
3
{
"greeting": "Hello, {name}"
}

4.格式化接口

日期:

  • formatDate
  • formatTime
  • formatRelative

数字:

  • formatNumber
  • formatPlural(This function should only be used in apps that only need to support one language. If your app supports multiple languages use formatMessage instead.)
  • formatNumber(1000); // “1,000” formatNumber(0.5, {style: ‘percent’}); // “50%” - formatNumber(1000, {style: ‘currency’, currency: ‘USD’}); // $1,000

字符串:

  • formatMessage
  • formatHTMLMessage
  • formatMessage(messages.greeting, {name: ‘Eric’})

复数:

“Hello, {name}, you have {itemCount, plural, =0 {no items} one { # item} other { # items}}.”

5.格式化组件

如果句子中有 html 元素,推荐使用 FormattedMessage 组件

1
2
3
4
5
<FormattedMessage 
id='app.greeting'
description='Greeting to welcome the user to the app' defaultMessage='Hello, {name}!'
values={{ name: <b>Eric</b> }}
/>

This allows messages to still be defined as a plain string without HTML — making it easier for it to be translated. At runtime, React will also optimize this by only re-rendering the variable parts of the message when they change. In the above example, if the user changed their name, React would only need to update the contents of the element.

6.提供 babel-plugin-react-intl 插件,自动提取待翻译文本,前提需要先用 defineMessages 进行定义,同时生成的 json 无法和现有的翻译工具(需要 po 文件格式)整合(react-intl-gettext 工具可以将 json 转为 po 文件)

1
2
3
4
5
6
7
8
9
const messages = defineMessages({ 
greeting: {
id: 'app.home.greeting',
description: 'Message to greet the user.',
defaultMessage: 'Hello, {name}!',
},
});

formatMessage('app.home.greeting', {name: ‘xxx’})
  1. 总结
  • 提供了国际化前端转换的完整方案(即字符串、时间、日期的转换)
  • 使用麻烦,需要指定 id,而不是默认语言作为 id,比如使用 formatMessage(‘你好’)
  • 没有提供完整解决方案,即只解决了如何从翻译文件解析转换,而没有解决如何自动生成翻译文件(babel-plugin-react-intl 不完善),整合使用现有的翻译工具
  • 文档和示例不完整、不清晰

方案二:使用 node-gettext + narp 构建一套完整的翻译工具链

  1. 使用 node-gettext 做为转换工具
1
2
3
4
5
import  Gettext  from  'node-gettext' 
import swedishTranslations from './translations/sv-SE.json'
const gt = new Gettext() gt.addTranslations('sv-SE', 'messages', swedishTranslations)
gt.setLocale('sv-SE')
gt.gettext('The world is a funny place')

node-gettext 提供了字符串的转换,没有提供日期、数字的转换:

GNU gettext features categories such as LC_MESSAGES, LC_NUMERIC and LC_MONETARY, but since there already is a plethora of great JavaScript libraries to deal with numbers, currencies, dates etc, node-gettext is simply targeted towards strings/phrases. You could say it just assumes the LC_MESSAGES category at all times.

另外,node-gettext 接受的是 json 文件

  1. 使用 narp 完成翻译文本自动提取,向 poeditor 或 transifex 上传待翻译文件和拉取翻译后的文件,和格式转换等功能
  • . narp push

Extraction of strings from source code (react-gettext-parser),
Merging upstream and local translations in order to support translations in branches (pot-merge)
Uploading POT resources to the translation service you use (Transifex or POEditor)

  • narp pull

Download PO translations from the translation service(丢弃掉旧的,全部用最新拉取的)
Converting PO to JSON (gettext-parser)
Writing the JSON translations to where you want it

  1. The JSON translations are formatted for node-gettext.(使用 node gettext 转换)
  • 使用 react-gettext-parser 提取翻译字符串,生成 pot 文件
  • 调用 poeditor 或 transifex 平台 API 上传下载翻译文件
  • 使用 gettext-parser Parse and compile gettext po and mo files to/from json
  1. 总结:
  • node-gettext 仅提供字符串的转换,数字和时间等,需要自己使用第三方库来实现,这个特点见仁见智,对于想一步到位的开发者而言,需要自己找第三方库,同时每个库都要单独进行 locale 配置,稍显麻烦;而对于需要定制化,或对性能要求高的开发者,则提供了一个扩展性很强的环境。
  • node-gettext 不支持插值,需要借助第三方插件
  • node-gettext 的语法简单通用
  • narp 封装了 poeditor 和 transifex 两个平台的 API,通过命令行可以实现快速地上传和下载翻译文件,同时实现了文件自动合并功能,省去在多分支情况下,遇到冲突时要手动合并的麻烦
  • 目前 narp 会将不同的翻译文件合成一个 json 文件,但支持的语种和待翻译语句较多时,产生的文件会很大,严重影响性能。最好的做法还是,不同语言生成到不同文件中,初始加载默认语言,在根据当前区域,动态加载所需语言(这种情况下切换语言的话,需要刷新才能得到更新,这种需求也比较合理)。

该方案的操作实例,可以参考这里 https://github.com/YaoKaiLun/i18n-example

方案三:i18n-next

前面的方案二,已经基本满足了我们的业务需求,而且其相对简单,容易理解。但如果你有更多的需求,或者你对国际化要求更高,那你可以尝试使用 i18n-next

I18next is an internationalization-framework written in and for JavaScript. But it’s much more than that.
i18next goes beyond just providing the standard i18n features such as (plurals, context, interpolation, format). It provides you with a complete solution to localize your product from web to mobile and desktop.

它支持以下特性:

  • Splitting translations into multiple files. Only load translations needed.
  • There are plugins to detect languages for most environments (browser, native, server). This enables to set priority of where to detect and even enables to cache set languages over requests / visits.
  • There are endless plugins to load translation from server, filesystem, … this backends also assert that loading gets retried on failure, or that a file does not get loaded twice and callbacks of success are only called once. Those backends can even provide an additional layer for local caching eg. in localStorage.
  • Options what to load and how to fallback depending on language.
  • Support for objects and arrays
  • Full control over management of the translations stored.
  • Rich system of events to react on changes important to your application.
  • Freedom of i18n formats - prefer ICU? Just use i18next-icu plugin.

同时,可以通过一些自动提取翻译语句和解析 PO 文件插件来实现方案二中 narp 的部分功能。

事实上,i18n-next 在 2011 年的时候就已经创建了。i18n-next 可以说提供了一个解决国际化问题的完整生态,除了基本的需求外,你还可以通过各种扩展,实现更多功能。而且相比 react-intl,他更活跃、更友好、更可靠一点。
No newline at end of file