- Published on
如何在开源项目中实现自动化i18n
- Authors
- Name
- McDaddy(戣蓦)
如何在开源项目中实现自动化i18n
当我们的产品需要出海时,不论前端还是后端都需要把国际化作为一个MUST的任务来完成,总的来说这是一个重复性大的苦力活,在不同的背景下也会有不同的实现方案
- 小项目或者偏静态的应用,会选择写中英文两套代码,然后通过路由来区别展示。
- 在基础建设比较完备的互联网大厂中,一般都会有自己的国际化平台,比如字节的Staring,阿里的美杜莎。这些平台都提供了一站式的国际化解决方案,比如词库的管理/自动翻译/人工校验/IDE 插件/版本管理。
- 开源项目或者小公司的大项目,它们一般无法借用大厂的内部基建,所以一般也会选择开源的解决方案,比如
i18next
、react-intl
等优秀的开源库
开源项目传统国际化流程
优秀开源项目的源码有个基本要求就是源码中不会出现中文,我们假设系统的主语言是英语,开发时的语言是中文,框架基于i18next
- 将需要国际化的文案用
i18n.d
包裹起来,如i18n.d('你好')
- 跑一个脚本,将所有被
i18n.d
包裹的文案提取出来,放到一个临时文件里去(一般是一个json文件),里面结构类似
{
"你好": ""
}
- 人工将上面的临时文件添加上自己的翻译,使得文件变成
{
"你好": "hello"
}
- 再跑一个脚本,将上面翻译好的英文回填到原来的位置,且将.d改为.t,
i18n.t('hello')
- 如果需要的话,在具体的文案前面还要加上命名空间,如
i18n.t('common:hello')
- 最后跑一个脚本,扫描全局代码,收集所有的文案作为key,然后在当前的locale文件中找匹配,如果找不到就去第二步的临时文件里找,最终生成新的locale文件
// zh.json
{
"common": {
"hello": "你好"
}
}
// en.json
{
"common": {
"hello": "hello"
}
}
- (非必须) 全局替换文案的key,这样做的好处是,即使将来中英文的文案都发生改变,文案依然是唯一的,不需要改source code,只需要改locale文件
// code.tsx
i18n.t('common:welcome_text');
// zh.json
{
"common": {
"welcome_text": "你好"
}
}
// en.json
{
"common": {
"welcome_text": "hello"
}
}
- 删除临时文件
当前痛点
- 整个流程步骤太多,团队要经过严格的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,不会有被限风险,也提供了基本免费的翻译能力
主要用到的库
- chokidar 更优质的
fs.watch
,用来监听文件变化 - translate Google Translate API 也兼容Yandex, Libre DeepL
- vinyl-fs 批量流式处理文件,用来批量扫描文件
- winston 前端的log4j工具,更优雅的log输出
仓库
https://github.com/McDaddy/babel-plugin-i18next
优点
- 框架无关,兼容React、Vue等前端框架
- 自动化资源与命名空间管理
- 支持Monorepo
- 支持interpolation
缺点
必须依赖babel,如swc,esbuild等工具无法正常支持