使用中文文案作为翻译的源

预计改进后效果

  1. 使用简单,方便程度接近于直接在源码中使用中文
  2. 自动化程度高,自动更新所有语种TS文件(小语种翻译有crowdin更新),自动生成发布qm文件。
  3. 合并维护简单,彻底解决之前TS文件合并冲突解决麻烦的问题。TS 文件冲突可以随意取一遍为准,后面会自动根据cpp中的源进行更新。
  4. 后期增加小语种方便

工程转换实现

从现方案zh_classin.ts自动生成 Lang_zh.cpp

这一步因为是一次性的工作,不在详述,过程简单描述如下(单独写了一个程序完成):

  1. 解析 Lang.cpp 里面的ID
  2. 根据 Lang.cpp 中的 ID 解析现有 zh_classin.ts (xml文件)中该 ID 的中文译文。
  3. 将 步骤2 解析出来的内容,按需要的格式写入 Lang_zh.cpp

Lang_zh.cpp 这个文件在大家后续编码过程中,处理国际化所需要关注的唯一一个文件。不在需要关注任何 TS 文件。

异常处理:ID_actionmultwrite_MultWriteID_ClassInBox_ItemResult 两个 ID 没有找到对应的中文,直接删除掉了这两个条目。

新工程实现细节

在对 Lang_zh.cpp 作了修改后(如增加词条或修改译文)后,编译代码时会自动做下面几个动作。

根据 Lang_zh.cpp 自动更新 TS 文件

lang 项目构建完成后,会自动更新 TS 文件。工程实现为 deploy_lang.pri 中的下列代码:

updateLang(zh_classin.ts, zh_CN)
updateLang(ar_classin.ts, ar_EG)
updateLang(en_classin.ts, en_GB)
updateLang(es_classin.ts, es_ES)
updateLang(fr_classin.ts, fr_FR)
updateLang(id_classin.ts, id_ID)
updateLang(ja_classin.ts, ja_JP)
updateLang(ko_classin.ts, ko_KR)
updateLang(pt_classin.ts, pt_PT)
updateLang(ru_classin.ts, ru_RU)
updateLang(vi_classin.ts, vi_VN)
updateLang(zh-tw_classin.ts, zh_TW)

updateLang 的具体实现祥见 deploy_lang.pri,即根据 Lang_zh.cpp, 更新 ts 文件到 ./lan_src/ 目录下,文件名由第一个参数指定,第二个参数指定了要生成 ts 文件到目标语言(上面的 zh_CN 为中文),如未指定,则根据文件名由Qt工具 lupdate 自动推断。目前都根据现有 ts 里面的值显式指定了。

备注: 因已经没有必要手动 lupdate 翻译文案,故在 lang.pro 删除了 TRANSLATION 字段,这样如果有人去手动 lupdate 也不会有效果。

删除的代码如下:

LANNAME=classin
TRANSLATIONS += $$PWD/lan/zh_$${LANNAME}.ts $$PWD/lan/zh-tw_$${LANNAME}.ts $$PWD/lan/en_$${LANNAME}.ts $$PWD/lan/ru_$${LANNAME}.ts $$PWD/lan/es_$${LANNAME}.ts $$PWD/lan/ja_$${LANNAME}.ts $$PWD/lan/ko_$${LANNAME}.ts $$PWD/lan/vi_$${LANNAME}.ts $$PWD/lan/ar_$${LANNAME}.ts $$PWD/lan/id_$${LANNAME}.ts $$PWD/lan/fr_$${LANNAME}.ts $$PWD/lan/pt_$${LANNAME}.ts

自动完成 zh_classin.ts 的中文翻译

deploy_lang.pri 中代码如下:

QMAKE_POST_LINK += $$quote($$COMMAND_SELF $${LANGUAGE_SRC_SED_DIR} $$escape_expand(\n\t))

LANGUAGE_SRC_SED_DIR: 为包含上一步生成的所有 ts 文件的目录
COMMAND_SELF: 为当前工程刚编译出来的 Lang命令(非Win平台)/Lang.exe (作为自动化工具,并不谁绿色包/安装包发布),主要实现见 lang/main.cpp

Lang命令/Lang.exe: 解析指定目录里的 zh_classin.ts 文件。把 标签中的中文直接作为中文译文,根据情形填入到 标签中。再去掉 标签中的 type=“unfinished” 属性(有的话)。
备注: 由上可见 zh_classin.ts 已不再具备可编辑性了,也没有必要进行人工编辑/修改。因为编译过程中会根据 Lang_zh.cpp 重新生成,并自动完成这个翻译过程。

一项小改进:Lang 工具同时会对相似ID(仅以大小写区分的ID)进行扫描,并在编译过程中输出报告。大家可根据情况看看是否修改成更具辨识性的 ID。

ID 扫描报告格式如下所示:

>>>>>>>>>>>>>>>>>>>>>>>
>> The following IDs are differ only in case, it is recommended to modify them.
>> -------------------
>> "ID_All, ID_all"
>> "ID_Auditor, ID_auditor"
>> "ID_Back, ID_back"
>> "ID_BlackboardContent_SaveToPngFailed, ID_BlackboardContent_SaveToPNGFailed"
>> "ID_Cancel, ID_cancel"
>> "ID_capture, ID_Capture"
>> "ID_continue, ID_Continue"
>> "ID_HeadsetPage_play, ID_HeadsetPage_Play"
>> "ID_hour, ID_Hour"
>> "ID_ImageFiles, ID_Imagefiles"
>> "ID_Loading, ID_loading"
>> "ID_LogIn, ID_Login"
>> "ID_Nickname, ID_NickName"
>> "ID_Ok, ID_OK, ID_ok"
>> "ID_Ok_MainWindow, ID_OK_MainWindow"
>> "ID_QClassRoomMgr_SetUpNow, ID_QClassRoomMgr_SetupNow"
>> "ID_reload, ID_Reload"
>> "ID_Restore, ID_restore"
>> "ID_Search_Class, ID_Search_class"
>> "ID_SettingsWidget_Hotkeys, ID_SettingsWidget_HotKeys"
>> "ID_ShareSelect_DesktopSharing, ID_ShareSelect_Desktopsharing"
>> "ID_Start, ID_start"
>> "ID_Student, ID_student"
>> "ID_Syncscroll, ID_SyncScroll"
>> "ID_teacher, ID_Teacher"
>> "ID_today, ID_Today"
>> "ID_Upgradenow, ID_UpgradeNow"
>> "ID_WebProxyError_AddLessonsFail, ID_WebProxyError_AddlessonsFail"
>> "ID_WebProxyError_ReplyTimeout, ID_WebProxyError_replyTimeout"
^^^^^^^^^^^^^^^^^^^^^

自动生成和发布 *.qm 文件

工程实现为 deploy_lang.pri 中的下列代码:

releaseLang(zh_classin.ts)
releaseLang(ar_classin.ts)
releaseLang(en_classin.ts)
releaseLang(es_classin.ts)
releaseLang(fr_classin.ts)
releaseLang(id_classin.ts)
releaseLang(ja_classin.ts)
releaseLang(ko_classin.ts)
releaseLang(pt_classin.ts)
releaseLang(ru_classin.ts)
releaseLang(vi_classin.ts)
releaseLang(zh-tw_classin.ts)

releaseLang 的具体实现祥见 deploy_lang.pri。这一步会对已有 ts 进行一些处理,对其中的 unfinshed 条目去掉 unfinished(正常不应该存在)。这一步在临时文件中处理,不会修改源ts文件。然后工程自动对处理后的 ts 生成 qm 文件并部署到目标位置。

备注: 如果字段还是 unfinished 生成 qm 会显示为 id,即是译文时存在的。所以加了自动对 ts 再处理,如果后面能保证没有这种情况,可以简化这一步的步骤。

其它小语种翻译

代码工程这边目前只会为个小语种自动生成 ts, 小语种译文由 杜敏松 那边的 crowdin 自动同步,具体过程我也不是太清楚。

处理流程图

前面这几个步骤的流程图表示:

graph TD
    A[main.cpp/Lang_zh.cpp 有更新]
    B[lan/*.ts 有更新
即从 crowdin 同步后的ts文件] A --> |构建工程| C{构建完成后} B --> |构建工程| C C --> |非中文翻译|D(拷贝 lan/*.ts 到 lan_src 目录下) C --> |中文翻译|E(删除 lan_src 目录下的 zh_classin.ts, 如果有) D --> |lupdate|F(以Lang_zh.cpp为源文件生成*.ts到lan_src目录下) E --> |lupdate|F F --> |非中文翻译|G(对lan_src/*.ts进一步处理
以消除文件中的 unfinished
处理后存放到 lan_release 目录) F --> |中文翻译|H(通过本工程生成的程序Lang对zh_classin.ts自动填充上译文
同时对只以大小写区分的ID列出报告输出) H --> G G --> |lrelease|I[对lan_release/*.ts发布qm文件到所需的目录]

总结

这个改进后,大家对国际化相关编码实操中,记住只需要在 Lang_zh.cpp 添加修改中文条目即可。编译运行后立马就能正确显示中文。

ts文件合并冲突中,任选一端为准即可(建议总是选远端为准),当然你有别的操作也可以,重要一点是解决了冲突即可(保证文件格式正确即可)。至少对中文来说,这一点是永远成立的。对于小语种我猜测,更合适的操作应该是以远端为准(即总是放弃本地),除非是 杜敏松 使用 crowdin 同步时。换句话就是小语种 ts 只应被工程自动化更新和crowdin同步(杜敏松)外无需任何人对其修改。 中文 ts 则是任何人都不应对其修改(修改也会背自动化覆盖)

已经修改工程文件,因正常情况无需修改 lan/*.ts(除杜敏松同步crowdin的译文外),故一般情况无需理会ts文件处理。

基于上面的原因,也就没有之前对 Lang 尽量不要拉分支的限制了。大家需要拉分支的时候直接拉即可,毕竟处理 Lang_zh.cpp 的合并冲突并不麻烦,甚至比普通代码的冲突还简单。


本文档为公司资源,请勿随意转发到外网。欢迎指出文章中的观点、引用来源的疑议以及任何有错误或不够清晰的表达。可以在下面评论区(暂未开放)评论或工作群里讨论。