Published on

如何在开源项目中实现自动化i18n

Authors
  • avatar
    Name
    McDaddy(戣蓦)
    Twitter

如何在开源项目中实现自动化i18n

当我们的产品需要出海时,不论前端还是后端都需要把国际化作为一个MUST的任务来完成,总的来说这是一个重复性大的苦力活,在不同的背景下也会有不同的实现方案

  • 小项目或者偏静态的应用,会选择写中英文两套代码,然后通过路由来区别展示。
  • 在基础建设比较完备的互联网大厂中,一般都会有自己的国际化平台,比如字节的Staring,阿里的美杜莎。这些平台都提供了一站式的国际化解决方案,比如词库的管理/自动翻译/人工校验/IDE 插件/版本管理。
  • 开源项目或者小公司的大项目,它们一般无法借用大厂的内部基建,所以一般也会选择开源的解决方案,比如i18nextreact-intl等优秀的开源库

开源项目传统国际化流程

优秀开源项目的源码有个基本要求就是源码中不会出现中文,我们假设系统的主语言是英语,开发时的语言是中文,框架基于i18next

  1. 将需要国际化的文案用i18n.d包裹起来,如i18n.d('你好')
  2. 跑一个脚本,将所有被i18n.d包裹的文案提取出来,放到一个临时文件里去(一般是一个json文件),里面结构类似
{
  "你好": ""
}
  1. 人工将上面的临时文件添加上自己的翻译,使得文件变成
{
  "你好": "hello"
}
  1. 再跑一个脚本,将上面翻译好的英文回填到原来的位置,且将.d改为.t,i18n.t('hello')
  2. 如果需要的话,在具体的文案前面还要加上命名空间,如i18n.t('common:hello')
  3. 最后跑一个脚本,扫描全局代码,收集所有的文案作为key,然后在当前的locale文件中找匹配,如果找不到就去第二步的临时文件里找,最终生成新的locale文件
// zh.json
{
  "common": {
    "hello": "你好"
  }
}

// en.json
{
  "common": {
    "hello": "hello"
  }
}
  1. (非必须) 全局替换文案的key,这样做的好处是,即使将来中英文的文案都发生改变,文案依然是唯一的,不需要改source code,只需要改locale文件
// code.tsx
i18n.t('common:welcome_text');

// zh.json
{
  "common": {
    "welcome_text": "你好"
  }
}

// en.json
{
  "common": {
    "welcome_text": "hello"
  }
}
  1. 删除临时文件

当前痛点

  • 整个流程步骤太多,团队要经过严格的SOP培训才能保证每个人都能正确执行国际化
  • 无法自动翻译文案,手动翻译效率太低
  • 需要手动管理国际化的key,取名繁琐且易错 {i18n.t('welcome_text')}
  • 无法自动地去复用之前可能已经翻译好的文案

为什么要有一个插件

国际化是一个毫无技术含量的体力活,如果能在写代码的同时自动完成就可以让Coder做到0负担,而loader插件可以做到在编译代码时自动运行,而不需要人工触发,降低了Coder的使用心智负担

要解决的问题

  • 在编写代码的过程中,自动得去完成i18n的抽取翻译和locale资源文件替换

  • 与原始的国际化方案需要兼容

  • 优化效率,尽量复用已有翻译

  • 更方便的命名空间管理,想象下现在如果想把一个词从A空间迁移到B空间要花几个步骤?也是因为这个手动成本太高,项目中经常存在非常多重复的翻译存在于不同的空间下

  • 生产模式下,如何确保未翻译的内容被不会被打包进去

  • Translate API的稳定性问题

实现过程

如何实现抽取和转化

loader实现法

最开始的实现版本是想写一个loader来通过正则表达式来匹配包装后的i18n源码,从而做到自动的抽取和翻译。但是发现的问题是正则有匹配错误的风险,最主要体现在正则很难忽略被注释的代码,同时要考虑各种奇怪的写法如何匹配,虽然可以适配但是成本比较高

// i18n.s('测试')

/**
i18n.s('测试')
*/

i18n.s('测试',  // 换行+注释
'fdp')

语法树实现法

相对而言效率更高,无匹配错误的方案。模仿了下babel-plugin-import的实现,通过访问者模式遍历语法树的同时还能做到生成新的语法树节点(i18n.t)替换原来的i18n.s

如何实现自动翻译

  • 在开发模式下,利用Pub/Sub模式(eventEmitter),启动一个文件监听,如果文件发生了变更,就会自动收集语法树解析后得到的未翻译的语句列表,然后交给Translate API解决

  • 接入Google Translate API,实现自动翻译,此处用到了三方包@vitalets/google-translate-api

如何解决翻译不稳定问题

Google提供的免费翻译API非常容易被限流,甚至有被墙的风险

  • 通过申请Google Cloud账号用免费流量来解决
  • 兼容有道翻译API,不会有被限风险,也提供了基本免费的翻译能力

主要用到的库

仓库

https://github.com/McDaddy/babel-plugin-i18next

优点

  • 框架无关,兼容React、Vue等前端框架
  • 自动化资源与命名空间管理
  • 支持Monorepo
  • 支持interpolation

缺点

必须依赖babel,如swc,esbuild等工具无法正常支持