likes
comments
collection
share

开发React/Vue应用的全流程国际化解决方案:VoerkaI18n

作者站长头像
站长
· 阅读数 2

关于

javascript国际化方案很多,比较有名的有fbti18nextreact-i18nextvue-i18nreact-intl等等,每一种解决方案均有大量的用户。为什么还要再造一个轮子?好吧,再造轮子的理由不外乎不满足于现有方案,总想着现有方案的种种不足之处,然后就撸起袖子开始干。

那么到底是对现有解决方案有什么不满?最主要有三点:

  • 大部份均为要翻译的文本信息指定一个key,然后在源码文件中使用形如$t("message.login")之类的方式,然后在翻译时将之转换成最终的文本信息。此方式最大的问题是,在源码中必须人为地指定每一个key,在中文语境中,想为每一句中文均配套想一句符合语义的英文key是比较麻烦的,也很不直观不符合直觉。我希望在源文件中就直接使用中文,如t("xxx万岁"),然后国际化框架应该能自动处理后续的一系列麻烦。

  • 要能够比较友好地支持多库多包monorepo场景下的国际化协作,当主程序切换语言时,其他包或库也可以自动切换,并且在开发上每个包或库均可以独立地进行开发,集成到主程序时能无缝集成。这点在现有方案上没有找到比较理想的解决方案。

基于此就开始造出VoerkaI18n这个全新的国际化多语言解决方案,主要特性包括:

  • 全流程支持

    从文本提取/自动翻译/编译/动态切换的全流程工程化支持,适用于大型项目

  • 集成自动翻译

调用在线翻译服务API支持对提取的文本进行自动翻译,大幅度提高工程效率

  • 符合直觉

在源码中直接使用符合直觉的翻译形式,不需要绞尽脑汁想种种key

  • 自动提取文本

提供扫描提取工具对源码文件中需要翻译的文本进行提取

  • TypeScript支持

    内置支持TypeScript类型以及生成TypeScript源码

  • 适用性

    支持任意Javascript应用,包括Nodejs/Vue/React/ReactNative等。

  • 多库联动

    支持多包工程下多库进行语言切换的联动

  • 工具链

    提供Vue/React/Babel等扩展插件,简化各种应用开发

  • 插值变量

    强大的插值变量机制,能扩展支持复数、日期、货币等灵活强大的多语言特性

  • 语言补丁

    在应用上线后发现错误时可以在线修复

  • 动态增加语种

    可以在应用上线后动态增加语种支持

  • 90%+测试覆盖率

    核心运行时超过90%的测试覆盖率

快速入门

本节以标准的Nodejs应用程序为例,简要介绍VoerkaI18n国际化框架的基本使用。

vuereact应用的使用流程也基本相同,可以参考Vue集成React集成

myapp
  |--package.json
  |--index.js  

在本项目的所有支持的源码文件中均可以使用t函数对要翻译的文本进行包装,简单而粗暴。

// index.js
console.log(t("xxx万岁"))
console.log(t("xxx成立于{}",1949))

t翻译函数是从myapp/languages/index.js文件导出的翻译函数,但是现在myapp/languages还不存在,后续会使用工具自动生成。voerkai18n后续会使用正则表达式对提取要翻译的文本。

第一步:安装命令行工具

安装@voerkai18n/cli到全局。

> npm install -g @voerkai18n/cli
> yarn global add @voerkai18n/cli
> pnpm add -g @voerkai18n/cli

第二步:初始化工程

在工程目录中运行voerkai18n init命令进行初始化。

> voerkai18n init 

上述命令会在当前工程目录下创建languages/settings.json文件。如果您的源代码在src子文件夹中,则会创建在src/languages/settings.json

settings.json内容如下:

{
    "languages": [
        {
            "name": "zh",
            "title": "zh",
            "default": true,    // 默认语言
        },
        {
            "name": "en",
            "title": "en"
        }
    ],
    "namespaces": {}
}

上述命令代表了:

  • 本项目拟支持中文英文两种语言。
  • 默认语言是中文(即在源代码中直接使用中文)
  • 激活语言是中文(代表当前生效的语言)

注意:

  • 可以修改该文件来配置支持的语言、默认语言、激活语言等。可支持的语言可参阅语言代码列表
  • voerkai18n init是可选的,voerkai18n extract也可以实现相同的功能。
  • 一般情况下,您可以手工修改settings.json,如定义名称空间。
  • voerkai18n init仅仅是创建languages文件,并且生成settings.json,因此您也可以自己手工创建。
  • 针对js/typescriptreact/vue等不同的应用,voerkai18n init可以通过不同的参数来配置生成ts文件或js文件。
  • 更多的voerkai18n init命令的使用请查阅这里

第三步:标识翻译内容

接下来在源码文件中,将所有需要翻译的内容使用t翻译函数进行包装,例如下:

import { t } from "./languages"
// 不含插值变量
t("xxx")
// 位置插值变量
t("xxx{}","万岁")
t("xxx成立于{}年,首都{}",1949,"北京")

t翻译函数只是一个普通函数,您需要为之提供执行环境,关于t翻译函数的更多用法见这里

第四步:提取文本

接下来我们使用voerkai18n extract命令来自动扫描工程源码文件中的需要的翻译的文本信息。 voerkai18n extract命令会使用正则表达式来提取t("提取文本")包装的文本。

myapp>voerkai18n extract

执行voerkai18n extract命令后,就会在myapp/languages通过生成translates/default.jsonsettings.json等相关文件。

  • translates/default.json : 该文件就是从当前工程扫描提取出来的需要进行翻译的文本信息。所有需要翻译的文本内容均会收集到该文件中。
  • settings.json: 语言环境的基本配置信息,包含支持的语言、默认语言、激活语言等信息。

最后文件结构如下:

myapp
  |-- languages
    |-- settings.json                // 语言配置文件
    |-- translates                   // 此文件夹是所有需要翻译的内容
      |-- default.json               // 默认名称空间内容
  |-- package.json
  |-- index.js

如果略过第一步中的voerkai18n init,也可以使用以下命令来为创建和更新settings.json

myapp>voerkai18n extract -D -lngs zh en de jp -d zh -a zh

以上命令代表:

  • 扫描当前文件夹下所有源码文件,默认是jsjsxhtmlvue文件类型。
  • 支持zhendejp四种语言
  • 默认语言是中文。(指在源码文件中我们直接使用中文即可)
  • 激活语言是中文(即默认切换到中文)
  • -D代表显示扫描调试信息,可以显示从哪些文件提供哪些文本

第五步:人工翻译

接下来就可以分别对language/translates文件夹下的所有JSON文件进行翻译了。每个JSON文件大概如下:

{
    "xxx万岁":{
        "en":"<在此编写对应的英文翻译内容>",
        "de":"<在此编写对应的德文翻译内容>",
        "jp":"<在此编写对应的日文翻译内容>",
        "$files":["index.js"]    // 记录了该信息是从哪几个文件中提取的
    },
	"xxx成立于{}":{
        "en":"<在此编写对应的英文翻译内容>",
        "de":"<在此编写对应的德文翻译内容>",
        "jp":"<在此编写对应的日文翻译内容>",
        "$files":["index.js"]
    }
}

我们只需要修改该文件翻译对应的语言即可。

重点:如果翻译期间对源文件进行了修改,则只需要重新执行一下voerkai18n extract命令,该命令会进行以下操作:

  • 如果文本内容在源代码中已经删除了,则会自动从翻译清单中删除。
  • 如果文本内容在源代码中已修改了,则会视为新增加的内容。
  • 如果文本内容已经翻译了一部份了,则会保留已翻译的内容。

总之,反复执行voerkai18n extract命令是安全的,不会导致进行了一半的翻译内容丢失,可以放心执行。

第六步:自动翻译

voerkai18n支持通过voerkai18n translate命令来实现调用在线翻译服务进行自动翻译。

>voerkai18n translate --appkey <在百度翻译上申请的密钥> --appid <在百度翻译上申请的appid>

在项目文件夹下执行上面的语句,将会自动调用百度的在线翻译API进行翻译,以现在的翻译水平而言,您只需要进行少量的微调即可。关于voerkai18n translate命令的使用请查阅后续介绍。

第七步:编译语言包

当我们完成myapp/languages/translates下的所有JSON语言文件的翻译后(如果配置了名称空间后,每一个名称空间会对应生成一个文件,详见后续名称空间介绍),接下来需要对翻译后的文件进行编译。

myapp> voerkai18n compile

compile命令根据myapp/languages/translates/*.jsonmyapp/languages/settings.json文件编译生成以下文件:

  |-- languages
    |-- settings.json                // 语言配置文件
    |-- idMap.js                     // 文本信息id映射表
    |-- index.js                     // 包含该应用作用域下的翻译函数等
    |-- storage.js
    |-- zh.js                        // 语言包
    |-- en.js
    |-- jp.js
    |-- de.js
    |-- formatters                   // 自定义扩展格式化器
        |-- zh.js                     
        |-- en.js
        |-- jp.js
        |-- de.js
    |-- translates                   // 此文件夹包含了所有需要翻译的内容
      |-- default.json
  |-- package.json
  |-- index.js

第八步:导入翻译函数

第一步中我们在源文件中直接使用了t翻译函数包装要翻译的文本信息,该t翻译函数就是在编译环节自动生成并声明在myapp/languages/index.js中的。

import { t } from "./languages"   

因此,我们需要在需要进行翻译时导入该函数即可。

但是如果源码文件很多,重次重复导入t函数也是比较麻烦的,所以我们也提供了一个babel/vite等插件来自动导入t函数,可以根据使用场景进行选择。

第九步:切换语言

当需要切换语言时,可以通过调用change方法来切换语言。

import { i18nScope } from "./languages"

// 切换到英文
await i18nScope.change("en")
// 或者VoerkaI18n是一个全局单例,可以直接访问
await VoerkaI18n.change("en")

i18nScope.changeVoerkaI18n.change两者是等价的。

一般可能也需要在语言切换后进行界面更新渲染,可以订阅事件来响应语言切换。

import { i18nScope } from "./languages"

// 切换到英文
i18nScope.on("change",(newLanguage)=>{
    // 在此重新渲染界面
    ...

})
// 
VoerkaI18n.on("change",(newLanguage)=>{
     // 在此重新渲染界面
     ...
})

@voerkai18n/vue@voerkai18n/react提供了相对应的插件和库来简化重新界面更新渲染。

第十步:语言包补丁

一般情况下,多语言的工程化过程就结束了,voerkai18n在多语言实践考虑得更加人性化。有没有经常发现这样的情况,当项目上线后,才发现:

  • 翻译有误
  • 客户对某些用语有个人喜好,要求你更改。
  • 临时要增加支持一种语言

一般碰到这种情况,只好重新打包构建工程,重新发布,整个过程繁琐而麻烦。 现在voerkai18n针对此问题提供了完美的解决方案,可以通过服务器来为应用打语言包补丁动态增加语言支持,而不需要重新打包应用和修改应用。

方法如下:

  1. 注册一个默认的语言包加载器函数,用来从服务器加载语言包文件。
import { i18nScope } from "./languages"

i18nScope.registerDefaultLoader(async (language,scope)=>{
    return await (await fetch(`/languages/${scope.id}/${language}.json`)).json()
})
  1. 将语言包补丁文件保存在Web服务器上指定的位置/languages/<应用名称>/<语言名称>.json即可。
  2. 当应用启动后会自动从服务器上加载语言补丁包合并,从而实现动为语言包打补丁的功能。
  3. 利用该特性也可以实现动态增加临时支持一种语言的功能

更完整的说明详见动态加载语言包语言包补丁功能介绍。