1、背景
前端应用如果想抢占国际市场,则必须先要做国际化改造。以使用React框架开发为例,目前常用的国际化组件是react-intl或其衍生品。但不论使用何组件,遵循的模式都大致如下:开发时在项目根目录下增加语言包文件,每个语言包使用key-value格式的js或json存储数据,在项目编译时会把全部语言包一起编译进js文件,在使用时会在本地或服务器存储当前语种,切换时由组件切换对应的展示文案。
2、存在的问题
实际在使用的过程中,虽然这种方式最简单,却随着开发的深入,就会发现这么做很多麻烦和不合理。
首先从开发体验上来讲,最简单的做法是每个语言包的文案都由开发手动维护,但当词条日益增多、支持语言多达二三十种时,再去手动维护语言包简直是人间噩梦,每次都得改动一大批文件,弄得我们在国际化上花费了很多不必要的时间,而且还不能保证准确性,可谓事倍功半。
其次从使用体验上来讲,随着语言包的数量增多和体积增大、势必导致最终编译的js文件过大,页面加载运行日益变慢,用户体验不断变差。
由此我们也将不得不对这种方式进行改进。
3、改进之路探索:一把辛酸泪
关于前端页面国际化改造,网上也是有不少的案例和经验,总结来看是从三个方面入手优化:文案采集、文案翻译、文案使用。
3.1、文案采集
首先当词条多、语种多时,手动登记肯定是不靠谱的。因此我们需要用一些工具来帮助我们对语言包进行自动采集更新。
3.1.1、从代码采集文案
我们可以借助babel工具,将我们的源代码转成以下格式的语法树(AST),然后遍历整个语法树获取到我们想要的国际化函数并获取其参数,最后转化成文案。
// 语法树样例
{
"type": "Program",
"body": [
{
"type": "VariableDeclaration",
"declarations": [
{
"type": "VariableDeclarator",
"id": {
"type": "Identifier",
"name": "answer"
},
"init": {
"type": "BinaryExpression",
"operator": "*",
"left": {
"type": "Literal",
"value": 6,
"raw": "6"
},
"right": {
"type": "Literal",
"value": 7,
"raw": "7"
}
}
}
],
"kind": "var"
}
]
}
这是网上给的最多的解决方案,但是这种方式还存在缺陷,即当函数参数是变量时即文案是后台文案或复杂的文案组合时就无法准确获取到文案,即存在类似下面的情况:
formatMessage({ key: `国家${a ? b : c}` }); // formatMessage是umi里国际化函数
为了解决部分词条不翻译或满足我们特定的解决方式,我们参考eslint的解决方案,在词条附近添加特定的注释来帮助我们的做一些自定义报错。但是这就需要对历史代码做处理,特别是一些老旧项目在改造时,如果遇到这种情况比较多时改起来程序员头发就得掉光。
3.1.2、从设计稿采集文案
如果项目开发流程严谨,每个页面开发时都有完整的设计稿(其实设计开发也应该要有完整设计稿,不然程序员怎么会知道做成啥样子),那么我们就可以从设计稿入手获取文案。因为设计稿不仅包含了页面的所有交互细节,也包含了页面所有的展示文案,只要我们能从中提取信息也不失为一种好办法。
目前设计软件有很多,大部分都支持自定义插件开发。以figma(很多大厂都是使用figma作为设计软件)为例,有figma开发平台(需要翻墙)给开发者插件开发指引,我们可以自己写一个插件,可以利用插件采集页面上的所有文案,手动过滤后即可得到文案。当文案比较复杂时也只需要插件支持用户自定义组合即可。
与第一个方案相比,从设计稿采集文案虽然需要更多的用户操作(用户选择文案组合方式和是否翻译),但是可以采集复杂的文案和后台文案。同时当产品经理修改文案时也可以通过插件更新设计稿,以保持设计稿最新(简直是设计师的福音)。
这种方式的缺点也很明显,如果程序员开发都没有设计稿,那就当我没说。
对于老旧项目,我们可以直接从语言包中采集旧文案,只需要对新做的页面按照这这种方式采集文案。
3.1.3、从产品文档采集文案
考虑到很多的程序员在开发时确实没有设计稿,我们也可以从产品设计文档中提取。要求产品经理将需要翻译的文案及其对应的翻译以表格形式放在文档固定的位置,然后再写个自定义工具扫描就能得到文案。与第一种方案相比,他也同样可以采集复杂的文案和后台文案,同时也不像第二种方案需要过多的用户操作。产品经理修改文案时也只需要对应的产品文档即可。
这种方式不方便对文案进行统一管理,会导致每个设计文档中都有零散的文案登记,会出现不同的产品经理对同一个文案有不同翻译的情况,同时这也相当于把程序员的体力活移给了产品经理,怎么说呢,相当不厚道(如果你觉得产品经理闲的老是给你们提伪需求,就当我这个句话没说)。
如果你们开发连设计文档都没有,那这个方案就不用考虑了,我只能说这个项目你们还能做下去简直是人间奇迹。
3.2、文案翻译
当获取到文案时,我们需要有一个地方统一存放和翻译文档,提供产品经理和程序员查询和翻译的功能。
3.2.1、使用在线协作表格
当文案和语种数量不多时,我们可以考虑使用在线协作表格进行文案进行统一管理,目前腾讯文档和飞书表格都是免费且强大的工具,我们只需要自定义一个工具将采集到的文案通过腾讯文档或飞书表格的API上传至表格,后续交由产品经理去管理、翻译即可。但是这种方式缺少流程化管控,当文案数量多时查询和管理起来也是不方便。
3.2.2、使用在线管理平台
当文案和语种都比较多时,我们可以考虑使用管理平台来进行管理。他的功能和腾讯文档、飞书表格差不多,好处是加入了流程化管理(上报-翻译-审批-使用),同时检索起来也更方便。但是目前国内尚无好用且免费的翻译平台,自研的成本又很高的,所以这种方式好多公司都是望洋兴叹。
这里给大家推荐几个国外免费的平台:
3.3、文案使用
当我们解决完文案采集和处理后,怎么使用便成了问题。目前用的最多的还是动态预加载和静态预编译两种。
动态预加载是指页面加载时加载部分或全部语言包,然后根据用户语言选择在页面上展示对应的语种的文案,这是目前最常用的解决方案,但是也是导致js包过大的原因。
静态预编译是指在项目打包根据语种将项目打包成多个语种的js版本,部署时通过页面路由切换,能降低js包大小,但是有多少个语种就得编译多少次,编译时间太长。另外一个需要对代码进行解析替换,就需要借助文案采集方案1,但是函数参数变量的问题和后台文案的问题不好解决。
综合上面两种方式的优缺,也可以选择一个折中方案。将语言包转成静态资源或请求,做成按需加载。我们需要在页面加载其他js前将语言包加载进内存并解析,然后再加载其他js,这样带来的代价就是增加了页面空白期。不过考虑到缓存机制和单个语言包大小,页面空白时间可控。如果不在页面加载其他js前加载完,就会导致使用到文案的变量在定义时获取不到文案,后续也不会再定义,也就相当于没效果。
再进一步考虑,如果我们能知道哪些个页面用了哪些个文案,当我们按路由给js分包时就可以在每个子页面加载前只加载该页面用到的文案,这样请求的大小将进一步减少,当然带来的代价就是请求会增多。如果我们能从设计稿中获取到文案,那么这种方式的可实现性很大。
4、最终方案考量因素
在最终方案选择的衡量因素中,除了要考虑技术方案的利弊和可实现性外,还得考虑开发流程中各个角色的配合和工作量。好的方案不该只方便程序员,应该方便所有人。因此先将页面国际化中的参与角色和工作整理如下:
- 产品经理:设计文案、修改文案及其翻译
- 设计师:设计设计稿、文案有修改时还得调整设计稿
- 程序员:编写语言包、文案有修改时修改本地语言包
5、基于figma插件的前端国际化解决方案
经过多方因素的衡量,最终选择开发figma插件并从figma设计稿中收集文案,收集完后上传至文案管理平台。平台会通知产品经理去翻译,翻译结束后平台会通知设计师去更新设计稿,然后设计师打开figma插件一键更新设计稿。同时平台也会通知程序员去重新编译项目,而在程序员开发时,平台会通知自定义的多语言组件自动更新本地语言包。由此产品经理、设计师、程序员三者的工作得到极大地解放。具体的流程见下图:
5.1、figma插件
figma插件的开发手册可以参见figma开发平台(需要翻墙)。插件会分析页面所有的文本节点,并按照回车符和样式进行截断,截断后设计师可以选择组合方式,例如存在文案“点击 进入 获取更多”,插件会截断成“点击”、“进入”、“获取更多”,设计师可以选择组合成“点击{x}获取更多”和“进入”两个文案或“点击”、“进入”、“获取更多”三个文案等多个组合方式。同时设计师也可以选择改文案是否选择翻译,一系列的操作将保存在figma文件中,为后续的自动更新设计稿做准备。
每个文案都会有一个独特的ID和一个语义化key,代码中使用的是语义化key,这样有助于开发者理解代码,而ID是伴随文案终生,key可能修改但是ID不会变,也是因此,我们可以根据ID来判断文案有无修改,有修改时通过插件更新设计稿上的文字节点。由此设计师通过插件即可完成他在国际化中需要的工作,提升工作效率。
5.2、文案管理平台
平台会接受插件上传的文案并和已有文案的内容进行比较,如果是未翻译过的文案则通知产品经理去翻译。产品经理也可以主动在上面进行文案调整,调整后会进入审批流程,交由制定人员审核后即可正式使用,而后平台便会发邮件告知设计师更新设计稿,也会通知开发人员去重新编译项目,同时也会将最新的文案信息推送给多语言组件。平台会标记每个文案的来源以及参考翻译结果,帮助产品经理快速定位文案使用位置且节省产品经理翻译时间。
5.3、多语言组件
考虑到可实现性,目前做成每个页面按照路由去加载语言包不太好做,我们可以退而求其次,选择在页面加载js前将单个语言包加载进内存。这就需要用到多语言组件,它包含4块内容: 第一块是独立的后台程序,他会在项目开发阶段使用,会时刻接受平台推送的最新文案信息并转成语言包保存在本地。 第二块也是独立的后台程序,会在项目编译节点从平台获取最新文案并转成语言包保存在本地,然后断开连接。 第三块是提供预处理函数,会页面加载其他js前从即在指定语种的静态资源语言包并处理保存进window对象中。 第四块是多语言函数,可以在代码中使用,可以展示对应语种的文本,本质是从window对象中获取对应key的文案并返回。 借助这个组件,程序员只需要在代码中直接使用文案,而不用在手动维护语言包,极大地解放双手,提升生产效率。
6、最后
经过我们项目上的实战使用,发现上述方案确实在开发效率和文案准确性方面都是很大的提升,也为设计师、产品经理省了不少事。同时我们也希望这篇文章也能对你们前端项目的国际化改造有一丝启发。如果对我们方案的实战产出感兴趣,可以通过公众号与小编进一步沟通。
我们是数数科技前端团队,目前负责游戏行业使用最多的用户行为分析系统的研发,同时也在积极探索前端新技术和新领域,如果你对游戏、大数据、可视化、工程化、全栈等方面有兴趣,欢迎加入我们,共创未来!
联系方式
- mail: young@thinkingdata.cn
- wx: ppxu1001
- website: www.thinkingdata.cn/
- more job: 数数信息科技(上海)有限公司 - 内部推荐