前言
國際化是軟件設(shè)計(jì)領(lǐng)域常見一個(gè)需求,其工作內(nèi)容常常包括:
- 泛客戶端國際化,包括 iOS、Android 和 Web
- 服務(wù)端國際化
- 國際化資源文件管理
- 項(xiàng)目之間、開發(fā)人員與翻譯者之間的協(xié)作
并且國際化方案常與具體技術(shù)棧綁定。
本套解決方案是在多端(iOS、Android 和 Web)確定各自國際化技術(shù)方案的基礎(chǔ)上,提供一套統(tǒng)一的國際化資源文件管理和相關(guān)合作伙伴協(xié)作的工作流。
需求痛點(diǎn)
目前,APP 端的國際化開發(fā)流程是,各端的開發(fā)人員會根據(jù)需求規(guī)格說明書在開發(fā)期間預(yù)定義待翻譯詞條對應(yīng)的 termId,然后翻譯人員后續(xù)把待翻譯的詞條列表以 excel 表格形式發(fā)送給各端人員,各端人員再根據(jù)對應(yīng)翻譯后的詞條,更新各自的翻譯文件。
以上的工作流程圖如下:
[圖片上傳失敗...(image-ef78cd-1614174010505)]
上述的工作流導(dǎo)致的問題在于:
- 翻譯人員將花費(fèi)大量時(shí)間在整理待翻譯詞條的表格,然后給到開發(fā)人員
- 開發(fā)人員拿到對應(yīng)翻譯后的詞條表格然后逐步比對各自的翻譯文件
- 多端開發(fā)人員自行定義 termId,大概率會出現(xiàn)同一詞條在不同平臺上不同的 termId 的命名
- 期間如果遇到缺漏翻譯的問題,開發(fā)人員需要再次向翻譯人員索要翻譯文本,翻譯工作與開發(fā)工作強(qiáng)耦合
- 缺少自動(dòng)化翻譯工具,翻譯人員翻譯工作繁重
一個(gè)最為理想的協(xié)作流程理應(yīng)是:
- 開發(fā)人員的待翻譯詞條不應(yīng)該從產(chǎn)品人員中獲取,而是從需要國際化的頁面的靜態(tài)文本中獲取,待翻譯的詞條空間是從開發(fā)期間提取出來的
- 開發(fā)人員將提取后的待翻譯詞條上傳到翻譯平臺,然后通知翻譯人員進(jìn)行翻譯工作
- 翻譯人員不用關(guān)注哪些詞條是需要待翻譯并且給到開發(fā)人員的,只關(guān)注于給到的待翻譯的詞條空間
- 在翻譯平臺首先利用自動(dòng)翻譯工具將語言進(jìn)行初步翻譯,類似英文翻譯,利用自動(dòng)翻譯工具有可能會存在詞法上的偏差,所以還需要參考業(yè)內(nèi)常用的國際化文案進(jìn)行二次修正,見 https://i18ns.com/
- 翻譯人員翻譯完成后通知開發(fā)人員,開發(fā)人員再從翻譯平臺下載翻譯后的文件
方案設(shè)計(jì)
前文提到一個(gè)最為理想的協(xié)作流程的愿景,但是理想是豐滿的,現(xiàn)實(shí)是骨感的。在實(shí)際的方案設(shè)計(jì)中需要結(jié)合目前多端、多平臺的差異性和項(xiàng)目的歷史技術(shù)債務(wù),綜合考慮得出一個(gè)對原有多端國際化技術(shù)影響的降到最小的解決方案。
根據(jù)客戶端同事反饋,原有的翻譯詞條達(dá)到 3000 多條,并且是以模塊為單位分散在各個(gè)文件夾下,如果將原有的翻譯文件納入新的工作流會導(dǎo)致重構(gòu)成本較大,因此本次多端統(tǒng)一國際化方案將不改變之前的翻譯文件的實(shí)施方式,而是對接下來的版本的多端國際化實(shí)施本套新的工作流;
多端國際化方案主要涉及以下方面:
- 第三方翻譯平臺的選取
- 多端國際化特點(diǎn)的差異化處理
- 翻譯文件的上傳與下載
第三方翻譯平臺的選取
第三方翻譯平臺將選擇 POEditor,該平臺除了提供基礎(chǔ)的可視化詞條翻譯界面外,還支持自動(dòng)翻譯功能用以提高翻譯效率以及開放 API 以打造自動(dòng)化工作流。
此外,POEditor 還支持多平臺、多格式的翻譯源文件,包括 .po、.pot、.xls、.csv、xml、.strings 等。
多端國際化特點(diǎn)的差異化處理
前文已經(jīng)提到國際化方案常與具體的技術(shù)棧綁定,因此不同的客戶端(iOS、Android 和 Web)在國際化方案的實(shí)現(xiàn)方法中存在一定的差異。通過抽象找到多端存在的共同特征和差異性,在具體方法實(shí)施中需要找到合適的切入點(diǎn)。
一般來說,無論是什么平臺以及使用何種技術(shù)棧,國際化技術(shù)離不開翻譯詞條 id 對應(yīng)特定語言的翻譯這一基本原理,這是多端的共性所在,特性在于不同的翻譯源文件可能在數(shù)據(jù)結(jié)構(gòu)形式上存在一定的差異。
以下是Web、iOS 和 Android 的翻譯源文件,
Web 是采用 GNU 的 Gettext 方案,從源代碼中提取出來的 pot 文件;
// Web
#: src/pages/Index/index.tsx:23
msgid "一個(gè)蘋果"
msgid_plural "%d 個(gè)蘋果"
msgstr[0] ""
#: src/pages/Index/index.tsx:20
msgid "你好,悅跑圈"
msgstr ""
#: src/pages/Index/index.tsx:26
msgid "姓名:%s"
msgstr ""
Android 和 iOS 一樣,本身在框架內(nèi)已經(jīng)定義好一套國際化的解決方案,Android 是在特定目錄下創(chuàng)建不同語言版本的 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 方案,從帶標(biāo)記的源碼中提取待翻譯的詞條生成合并后的 pot 文件,因此其對于的 termId 就是當(dāng)時(shí)標(biāo)記時(shí)的文本,表現(xiàn)為純中文;
而 Android 和 iOS 本身就從代碼組織層面講待翻譯的詞條從頁面中剝離,形成單獨(dú)的 xml 或是 strings 文件,頁面在引入各自的翻譯文件時(shí)需要根據(jù)對應(yīng)的 termId 去調(diào)用函數(shù)去設(shè)值,這里就會產(chǎn)生一個(gè)與 web 端所沒有的問題,初始化時(shí)的 termId 如何進(jìn)行定義?
與 web 端不同,termId 不是直接從帶標(biāo)記的文本中提取,而是直接寫在各自的獨(dú)立的翻譯文件中,并且 termId 必須使用英文進(jìn)行標(biāo)識。此外,由于 iOS 和 Android 分屬不同平臺,如何避免同一個(gè)頁面各自定義不同的 termId?
<colgroup><col><col><col><col></colgroup>
|
端或平臺
|
國際化技術(shù)框架
|
詞條提取方式
|
翻譯文件格式
|
|
Web 端
|
GNU 的 Gettext
|
從標(biāo)記的源碼中提取,termId 就是源碼中標(biāo)記時(shí)的文本
|
抽取的待翻譯文件是 pot 文件,翻譯后的文件采用 json 文件
|
|
iOS 端
|
框架限定
|
單獨(dú)的文件夾存放翻譯文件,無需從源碼中提取,但是 termId 需要預(yù)定義
|
翻譯前后的文件均為 .strings 文件
|
|
Android 端
|
框架限定
|
同上
|
翻譯前后的文件均為 string.xml 文件
|
方案輸出 *****
鑒于以上出現(xiàn)的問題,這里給出的解決方案如下:
對于 Web 端詞條 id 生成的特殊性,單獨(dú)建立一個(gè) POEDitor 項(xiàng)目,其將獨(dú)立于客戶端項(xiàng)目,自動(dòng)從源碼抽取帶標(biāo)記的文本生成對應(yīng)的翻譯文件然后上傳至翻譯平臺;
而對于 iOS 和 Android,則需要產(chǎn)品人員事先在需求規(guī)格說明書時(shí)就要在翻譯平臺初始化一份當(dāng)前版本新增的待翻譯詞條的默認(rèn)語言的源翻譯文件以約束開發(fā)人員使用統(tǒng)一一套 termId, 初始化后產(chǎn)品人員通知開發(fā)人員從翻譯平臺下載最新版本的默認(rèn)語言翻譯文件進(jìn)行開發(fā)工作,等到翻譯人員將多國語言的翻譯完成后,再次通知開發(fā)人員下載完整的翻譯后的文件。
值得注意的是,經(jīng)過和客戶端的同事溝通,原有的翻譯文件的組織方式是分散在各個(gè)模塊中,這樣一來采用多端國際化解決方案對于項(xiàng)目本地的翻譯文件管理就會造成一定的困難。因此,在后續(xù)的版本迭代中,iOS 和 Android 端將會獨(dú)立出一個(gè)公共的模塊用于統(tǒng)一管理國際化文件,其他模塊將會從這個(gè)公共的國際化模塊中引入翻譯后的文件,翻譯文件的上傳和下載也就針對該份文件。
下面舉個(gè)例子,例如假設(shè)新增的頁面涉及如下詞條,則產(chǎn)品人員可先在 POEditor 進(jìn)行形式化的 termId 定義:
<colgroup><col><col><col></colgroup>
|
termId
|
name
|
remark
|
|
login.btn.comfirm(Andrord 平臺內(nèi)在機(jī)制使用點(diǎn)分命名會產(chǎn)生命名變更)
login_btn_confirm
|
確認(rèn)
|
登錄頁確認(rèn)按鈕
|
|
run_btn_startRun
|
開始跑步
|
跑步頁開始跑步按鈕
|
termId 的命名規(guī)則大體上是 <頁面> + <控件類型> + <文本值>,待將本次版本更新后的初始化翻譯文件定義好后,通知開發(fā)人員下載默認(rèn)語言的翻譯清單。
// string.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="login_btn_confirm">確認(rèn)</string>
</resources>
// locales.strings
"login_btn_confirm" = "確認(rèn)"
這樣一來客戶端就可以采用同一套默認(rèn)語言源文件去初始化各自不同平臺的翻譯文件,等到后續(xù)翻譯人員將多國語言翻譯好后,開發(fā)人員只需直接從 POEditor 下載即可。
372px上述的多端國際化工作流變?yōu)椋?/p>
[圖片上傳失敗...(image-2c58ae-1614174010505)]
下面討論一下這個(gè)解決方案的優(yōu)缺點(diǎn),
優(yōu)點(diǎn)在于即便是不同平臺的客戶端,其翻譯文件都是通過統(tǒng)一一套 termId 去標(biāo)識詞條,有利于打造多端統(tǒng)一國際化工作流;由于 POEditor 充當(dāng)類似”中間層“的角色,因此翻譯人員只需在翻譯平臺編輯詞條,而開發(fā)人員也只需從翻譯平臺上傳或下載詞條,實(shí)現(xiàn)工作流上的解耦。
缺點(diǎn)是開發(fā)人員需要等待產(chǎn)品人員事先提供一份待翻譯的詞條清單,一旦頁面存在缺漏或是需求上的變動(dòng),又需要產(chǎn)品人員給出變動(dòng)的詞條清單。這樣一來,產(chǎn)品和開發(fā)人員的工作又會陷入一定的耦合境地,但是從總體數(shù)量看,問題規(guī)模不會太大。
翻譯文件的上傳與下載
POEditor 提供 Open API 用以實(shí)現(xiàn)翻譯文件的上傳和下載功能,這里為了多端開發(fā)人員便于從 POEditor 平臺下載或上傳翻譯文件,將計(jì)劃打造一款基于 node 的命令行工具 poeditor,下載地址 here。
提供 pull 和 push 兩個(gè)命令。
按照 Node.js 環(huán)境,https://nodejs.org/en/;
全局按照 poeditor.cli 命令行工具
$ npm i -g poeditor.cli
# 下載 upstream 更新后的翻譯文件
$ poeditor pull
# 上傳 downstream 新增或修改的翻譯文件
$ poeditor push
當(dāng)運(yùn)行上述命令時(shí),命名行會自動(dòng)讀取當(dāng)前項(xiàng)目根路徑下的 poeditor-config.json 配置文件;
{
"apiToken": "", // poeditor token
"projectId": "", // 項(xiàng)目 id
"fileType": "", // 下載的文件類型,可支持 (po, pot, mo, xls, csv, resw, resx, android_strings, apple_strings, xliff, properties, key_value_json, json, xmb, xtb)
"targetDir": "", // 本地翻譯文件文件夾
}
值得開發(fā)人員注意的是,雖然命令行提供 push 操作,但是不建議使用該命令,因?yàn)橛捎诓煌脚_的占位符的差異性所以會導(dǎo)致上傳的詞條會污染上游詞條。
翻譯人員的相關(guān)工作
翻譯人員需要登錄到 poeditor 平臺,初始化一份默認(rèn)語言的的翻譯詞條清單。
[圖片上傳失敗...(image-6610d-1614174010505)]
翻譯人員在翻譯詞條時(shí)有以下幾點(diǎn)需要注意:
- 所有詞條的 Id 統(tǒng)一采用 <頁面><控件類型><文本值> 的定義形式
- 翻譯平臺統(tǒng)一約定占位符為 '{variable} xxx',例如 '{count} 蘋果',iOS 在下載翻譯文件的時(shí)候,命令行工具會自動(dòng)替換為 '%@蘋果',Android 則會替換為 '%n$s蘋果'
- 對于存在復(fù)數(shù)類型的詞條,統(tǒng)一使用兩個(gè)不同的 termId,如 app_fruit_apple 和 app_fruit_apples