多端统一国际化

前言

国际化是软件设计领域常见一个需求,其工作内容常常包括:

  • 泛客户端国际化,包括 iOS、Android 和 Web
  • 服务端国际化
  • 国际化资源文件管理
  • 项目之间、开发人员与翻译者之间的协作

并且国际化方案常与具体技术栈绑定。

本套解决方案是在多端(iOS、Android 和 Web)确定各自国际化技术方案的基础上,提供一套统一的国际化资源文件管理和相关合作伙伴协作的工作流。

需求痛点

目前,APP 端的国际化开发流程是,各端的开发人员会根据需求规格说明书在开发期间预定义待翻译词条对应的 termId,然后翻译人员后续把待翻译的词条列表以 excel 表格形式发送给各端人员,各端人员再根据对应翻译后的词条,更新各自的翻译文件。

以上的工作流程图如下:

[图片上传失败...(image-ef78cd-1614174010505)]

上述的工作流导致的问题在于:

  • 翻译人员将花费大量时间在整理待翻译词条的表格,然后给到开发人员
  • 开发人员拿到对应翻译后的词条表格然后逐步比对各自的翻译文件
  • 多端开发人员自行定义 termId,大概率会出现同一词条在不同平台上不同的 termId 的命名
  • 期间如果遇到缺漏翻译的问题,开发人员需要再次向翻译人员索要翻译文本,翻译工作与开发工作强耦合
  • 缺少自动化翻译工具,翻译人员翻译工作繁重

一个最为理想的协作流程理应是:

  • 开发人员的待翻译词条不应该从产品人员中获取,而是从需要国际化的页面的静态文本中获取,待翻译的词条空间是从开发期间提取出来的
  • 开发人员将提取后的待翻译词条上传到翻译平台,然后通知翻译人员进行翻译工作
  • 翻译人员不用关注哪些词条是需要待翻译并且给到开发人员的,只关注于给到的待翻译的词条空间
  • 在翻译平台首先利用自动翻译工具将语言进行初步翻译,类似英文翻译,利用自动翻译工具有可能会存在词法上的偏差,所以还需要参考业内常用的国际化文案进行二次修正,见 https://i18ns.com/
  • 翻译人员翻译完成后通知开发人员,开发人员再从翻译平台下载翻译后的文件

方案设计

前文提到一个最为理想的协作流程的愿景,但是理想是丰满的,现实是骨感的。在实际的方案设计中需要结合目前多端、多平台的差异性和项目的历史技术债务,综合考虑得出一个对原有多端国际化技术影响的降到最小的解决方案。

根据客户端同事反馈,原有的翻译词条达到 3000 多条,并且是以模块为单位分散在各个文件夹下,如果将原有的翻译文件纳入新的工作流会导致重构成本较大,因此本次多端统一国际化方案将不改变之前的翻译文件的实施方式,而是对接下来的版本的多端国际化实施本套新的工作流;

多端国际化方案主要涉及以下方面:

  • 第三方翻译平台的选取
  • 多端国际化特点的差异化处理
  • 翻译文件的上传与下载

第三方翻译平台的选取

第三方翻译平台将选择 POEditor,该平台除了提供基础的可视化词条翻译界面外,还支持自动翻译功能用以提高翻译效率以及开放 API 以打造自动化工作流。

此外,POEditor 还支持多平台、多格式的翻译源文件,包括 .po、.pot、.xls、.csv、xml、.strings 等。

多端国际化特点的差异化处理

前文已经提到国际化方案常与具体的技术栈绑定,因此不同的客户端(iOS、Android 和 Web)在国际化方案的实现方法中存在一定的差异。通过抽象找到多端存在的共同特征和差异性,在具体方法实施中需要找到合适的切入点。

一般来说,无论是什么平台以及使用何种技术栈,国际化技术离不开翻译词条 id 对应特定语言的翻译这一基本原理,这是多端的共性所在,特性在于不同的翻译源文件可能在数据结构形式上存在一定的差异。

以下是Web、iOS 和 Android 的翻译源文件,

Web 是采用 GNU 的 Gettext 方案,从源代码中提取出来的 pot 文件;

// Web
#: src/pages/Index/index.tsx:23
msgid "一个苹果"
msgid_plural "%d 个苹果"
msgstr[0] ""

#: src/pages/Index/index.tsx:20
msgid "你好,悦跑圈"
msgstr ""

#: src/pages/Index/index.tsx:26
msgid "姓名:%s"
msgstr ""

Android 和 iOS 一样,本身在框架内已经定义好一套国际化的解决方案,Android 是在特定目录下创建不同语言版本的 string.xml 文件,而 iOS 则是 xxx.strings 文件。

// Android string.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="aitrain">AI Speech Training</string>
</resources>

// iOS Localizable.strings
"aitrain" = "AI Speech Training";

三者在词条提取方法和词条 id 的定义上存在一定的不同。

目前,web 端是采用 GNU 的 gettext 方案,从带标记的源码中提取待翻译的词条生成合并后的 pot 文件,因此其对于的 termId 就是当时标记时的文本,表现为纯中文;

而 Android 和 iOS 本身就从代码组织层面讲待翻译的词条从页面中剥离,形成单独的 xml 或是 strings 文件,页面在引入各自的翻译文件时需要根据对应的 termId 去调用函数去设值,这里就会产生一个与 web 端所没有的问题,初始化时的 termId 如何进行定义?

与 web 端不同,termId 不是直接从带标记的文本中提取,而是直接写在各自的独立的翻译文件中,并且 termId 必须使用英文进行标识。此外,由于 iOS 和 Android 分属不同平台,如何避免同一个页面各自定义不同的 termId?

<colgroup><col><col><col><col></colgroup>
|

端或平台

|

国际化技术框架

|

词条提取方式

|

翻译文件格式

|
|

Web 端

|

GNU 的 Gettext

|

从标记的源码中提取,termId 就是源码中标记时的文本

|

抽取的待翻译文件是 pot 文件,翻译后的文件采用 json 文件

|
|

iOS 端

|

框架限定

|

单独的文件夹存放翻译文件,无需从源码中提取,但是 termId 需要预定义

|

翻译前后的文件均为 .strings 文件

|
|

Android 端

|

框架限定

|

同上

|

翻译前后的文件均为 string.xml 文件

|

方案输出 *****

鉴于以上出现的问题,这里给出的解决方案如下:

对于 Web 端词条 id 生成的特殊性,单独建立一个 POEDitor 项目,其将独立于客户端项目,自动从源码抽取带标记的文本生成对应的翻译文件然后上传至翻译平台;

而对于 iOS 和 Android,则需要产品人员事先在需求规格说明书时就要在翻译平台初始化一份当前版本新增的待翻译词条的默认语言的源翻译文件以约束开发人员使用统一一套 termId, 初始化后产品人员通知开发人员从翻译平台下载最新版本的默认语言翻译文件进行开发工作,等到翻译人员将多国语言的翻译完成后,再次通知开发人员下载完整的翻译后的文件。

值得注意的是,经过和客户端的同事沟通,原有的翻译文件的组织方式是分散在各个模块中,这样一来采用多端国际化解决方案对于项目本地的翻译文件管理就会造成一定的困难。因此,在后续的版本迭代中,iOS 和 Android 端将会独立出一个公共的模块用于统一管理国际化文件,其他模块将会从这个公共的国际化模块中引入翻译后的文件,翻译文件的上传和下载也就针对该份文件。

下面举个例子,例如假设新增的页面涉及如下词条,则产品人员可先在 POEditor 进行形式化的 termId 定义:

<colgroup><col><col><col></colgroup>
|

termId

|

name

|

remark

|
|

login.btn.comfirm(Andrord 平台内在机制使用点分命名会产生命名变更)

login_btn_confirm

|

确认

|

登录页确认按钮

|
|

run_btn_startRun

|

开始跑步

|

跑步页开始跑步按钮

|

termId 的命名规则大体上是 <页面> + <控件类型> + <文本值>,待将本次版本更新后的初始化翻译文件定义好后,通知开发人员下载默认语言的翻译清单。

// string.xml
<?xml version="1.0" encoding="utf-8"?>
    <resources>
      <string name="login_btn_confirm">确认</string>
    </resources>

// locales.strings
"login_btn_confirm" = "确认"

这样一来客户端就可以采用同一套默认语言源文件去初始化各自不同平台的翻译文件,等到后续翻译人员将多国语言翻译好后,开发人员只需直接从 POEditor 下载即可。

372px上述的多端国际化工作流变为:

[图片上传失败...(image-2c58ae-1614174010505)]

下面讨论一下这个解决方案的优缺点,

优点在于即便是不同平台的客户端,其翻译文件都是通过统一一套 termId 去标识词条,有利于打造多端统一国际化工作流;由于 POEditor 充当类似”中间层“的角色,因此翻译人员只需在翻译平台编辑词条,而开发人员也只需从翻译平台上传或下载词条,实现工作流上的解耦。

缺点是开发人员需要等待产品人员事先提供一份待翻译的词条清单,一旦页面存在缺漏或是需求上的变动,又需要产品人员给出变动的词条清单。这样一来,产品和开发人员的工作又会陷入一定的耦合境地,但是从总体数量看,问题规模不会太大。

翻译文件的上传与下载

POEditor 提供 Open API 用以实现翻译文件的上传和下载功能,这里为了多端开发人员便于从 POEditor 平台下载或上传翻译文件,将计划打造一款基于 node 的命令行工具 poeditor,下载地址 here

提供 pull 和 push 两个命令。

按照 Node.js 环境,https://nodejs.org/en/

全局按照 poeditor.cli 命令行工具

$ npm i -g poeditor.cli

# 下载 upstream 更新后的翻译文件
$ poeditor pull
# 上传 downstream 新增或修改的翻译文件
$ poeditor push

当运行上述命令时,命名行会自动读取当前项目根路径下的 poeditor-config.json 配置文件;

{
  "apiToken": "",       // poeditor token
  "projectId": "",      // 项目 id
  "fileType": "",       // 下载的文件类型,可支持 (po, pot, mo, xls, csv, resw, resx, android_strings, apple_strings, xliff, properties, key_value_json, json, xmb, xtb)
  "targetDir": "",      // 本地翻译文件文件夹
}

值得开发人员注意的是,虽然命令行提供 push 操作,但是不建议使用该命令,因为由于不同平台的占位符的差异性所以会导致上传的词条会污染上游词条。

翻译人员的相关工作

翻译人员需要登录到 poeditor 平台,初始化一份默认语言的的翻译词条清单。

[图片上传失败...(image-6610d-1614174010505)]

翻译人员在翻译词条时有以下几点需要注意:

  • 所有词条的 Id 统一采用 <页面><控件类型><文本值> 的定义形式
  • 翻译平台统一约定占位符为 '{variable} xxx',例如 '{count} 苹果',iOS 在下载翻译文件的时候,命令行工具会自动替换为 '%@苹果',Android 则会替换为 '%n$s苹果'
  • 对于存在复数类型的词条,统一使用两个不同的 termId,如 app_fruit_apple 和 app_fruit_apples
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,012评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,589评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,819评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,652评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,954评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,381评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,687评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,404评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,082评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,355评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,880评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,249评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,864评论 3 232
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,007评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,760评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,394评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,281评论 2 259

推荐阅读更多精彩内容