之前在做 blog 多语言支持的时候,也曾经短暂的做过 hugo 构建的拦截进行 AI 机翻,奈何 GPT4 成本太高和效果自己无法验证放弃了。 而后做 Web SaaS(Vue3 + Vite + TS + i18n) 项目,需要到多语言的支持。重新设计了一套满足快速定位,和自动 AI 机翻的多语言方案。
满足以下需求:
设计需求
- 避免全量加载利用率不高的资源,支持分包加载。
- 语言切换后直接 Reload Page
- 分包就涉及到 Key 相同问题,支持命名空间
- 开发便利性
- 自动机翻
- 导出母审表格(todo)
- 更新校正内容(todo)
设计方案
现在除了导出母审和更新审核的内容未完成,其它都已完成,但设计方案也还是可以作为参考。
分包
架构渐进式演进过程中,很大情况就是在全局统一一个对应的 [local].json
就不在往下拆了,重复的 key 也能很快的找到,再写上个自动 load files 的脚本,项目运行时执行,将语言内容都加载上,按照语言去初始。如果您是处于这样的代码,谁写的就上去抽他丫的两大笔兜,自己写的抽自己(我先来)。
假设:需要支持 10 种语言,每种 60KB,首页需要 load 600KB,传输速率 1M/s;那么就得多花差不多 500ms,还不算流量计费的成本。
所以需要根据使用频率,按 Golbal、Views、Components 等需要的时机去控制加载,但相应的,会增加请求数量。
命名空间
另外,不管是何种文件拆分方式,也都可能会遇到同 Key 不同 Value 的场景,这还是比较好处理的,增加独立的命名空间就行。
幸运的是,"vue-i18n": "^11.1.9"
基础能力能够满足需求。
开发便利性
在基础要求之外的开发便利上,直觉上我们会以 Golbal.common.[key]
或者 [namespace].[key]
这样的方式,去读取;需要修改 Value 可能需要先找到 Key,再去找到 Key 使用的地方,再结合同 key 名的情况,有时候还是挺费劲的事儿(同时使用 tailwind)。
这里我选择基于 EN 作为 Base,Key === Value。
可能有的人会说,啊,这么多特殊字符,这么长的 Value 你用来当 Key?!
除了超长或者同 Key 不同 Value 需要单独处理之外。每个 Key(Value)都可以看作唯一的,定位到 (Key)Value 等效;只要内容发生改变,Key 也就废弃了,相当于完成了原 Key 的删除,和新内容 Key 的新增。当然,你也可以直接修改 Value 不修改 Key 使用的代码,更少改动完成内容更新,也许会出现 Key(Value)含义对应不上的小问题。
自动机翻
再一个就是自动化机翻的考虑,依旧选择 EN 作为 Base。你必须要副本一份 EN 旧文件(首次可以是空)才能计算出来新、删、改(直接从目标语言对比 Base 语言,无法得到修改的)的 Key,而后依旧要和目标语言进行对比(否则后增加的语言文件,可能缺少数据),调用接口机翻到目标语言的操作。
Key(Value)相同则可以直接使用目标语言进行对比,因为不存在“改”这个选项了,就不用在每个 Base 语言的文件隔壁,自动“拉”一个副本文件了。
当然,以上的开发便利性和机翻的做法,是仁者见仁智者见智了。
导出母审表格(todo)
以审核要求的格式来,正常就是以 Base 的 Key(Value)导出成表格,发给对应翻译员审核回填后返回。
更新校正内容(todo)
有文件都好自动化解析,回写。
实现方式
分包加载 & 命名空间
// 构建映射
const i18nModules = import.meta.glob('/src/**/i18n/*.ts')
// ...
const module = await i18nModules[moduleKey]()
卸载同理,获取所有 key,删除目标内容,重新 set 就好。
// 使用 vue-i18n 内置的 mergeLocaleMessage 方法
const namespacedMessages = { [namespace]: messages }
i18n.global.mergeLocaleMessage(locale, namespacedMessages)
// or i18n.global.setLocaleMessage(locale, updatedMessages)
简单代码样例:https://github.com/hawkeye-xb/code-share/blob/main/i18n/index.ts
Diff & 机翻
按照 Key === Value 的结构,代码应该都挺简单的。DFS(BFS)遍历下所有 i18n 目录,获取 EN Base ,直接进行 Diff 和 调用机翻 API 就好。