规模化 i18n 翻译流水线
十个资产给在 10+ 语言发版、又不想被 SaaS 按字数收费的应用团队。CI 驱动流水线:Weblate/Tolgee 抽 key → OpenAI SDK 或自托管 Transformers/LibreTranslate 翻译 → Vale 检查术语表 → LanguageTool 做语法 QA → typos 拼写校对 → 回注。pre-commit 和 markdownlint 在每个 PR 上守门。
这个 pack 包含什么
这套给在 10+ 语言发版、又没有本地化外包的应用团队 —— 一个后端工程师、一个前端工程师、一个兼职 PM,三个人轮 on-call,付不起按字数线性扩张的 SaaS 账单。任务从「翻译字符串」变成「在每个 PR 上保持翻译和 main 同步,且批量路径里没人参与」。
这不是 译者的多语种栈 的活。那套是给在流水线里有真人译者参与的本地化工程师用的 —— Weblate、术语表 owner、post-edit 工作流、PDF/视频格式工具。这套是给想把流水线端到端自动化、只在门禁失败时升级到真人审稿的工程团队。还是同样五段 —— 抽取、翻译、QA、校验、回注 —— 但工具选择变了,因为操作者变了。
差异体现在工具上。Weblate 和 Tolgee 保留,因为任何流水线都还需要 TMS;但加入了 pre-commit(CI 门禁编排)、typos(CI 友好的拼写检查)、markdownlint(防止译文 .md 把文档构建打挂)、openai-python SDK(你翻译脚本里调 LLM 的客户端)、transformers(让你能用自己的 GPU 微调 NMT,不付 API 费)。批量翻译路径从「点鼠标」变成「写代码」。
推荐安装顺序(抽取 → AI 翻译 → QA 门禁 → 回注)
- Weblate —— 持有源真相的 TMS。从这里开始,因为后面每件工具不是喂它就是读它。Weblate 监控你的 git 仓库,从 gettext/xliff/json/Android XML/properties 里抽字符串,把完成的译文以 commit 推回。Docker 自托管,对接你现有的 GitHub/GitLab,这是地基。
- Tolgee —— 开发者友好的备选。如果你的审稿人是 PM 和设计师,需要在跑起来的 app 上 alt-click 看字符串上下文,选 Tolgee;如果审稿人就活在 PR 里,选 Weblate。两个都列出来,是因为正确答案取决于「谁审译文」,不取决于技术。
- LibreTranslate —— 跑批量路径的自托管 NMT。接到 Weblate 的自动建议后端,每个新字符串在被人看到之前先过一遍机翻。零按 token 计费、零速率限制、零向第三方 SaaS 发送未发布字符串的合规审查。UI 字符串前 80% 走 LibreTranslate 不再升级。
- Hugging Face Transformers —— 当 LibreTranslate 的 Argos 模型对你的目标语种不够流畅、需要微调时用它。加载 NLLB-200 或 M2M-100,用你已有的翻译记忆库(从 Weblate 导出 TMX)微调,然后挂自家 GPU 部署。这是低资源语言和 post-edit 重度场景下、现成 NMT 流畅度掉线时的逃生口。
- openai-python(或任意 LLM SDK) —— 给「必须像真人写的」字符串用的上下文感知翻译器。营销文案、用户看到的错误信息、新手引导。你的翻译脚本读取「源字符串 + 截图 URL + 术语表 + 最近 3 条相似字符串译文」,构造 prompt,调 LLM,写回 Weblate。永远把术语表带进 prompt。永远。
- Vale —— 术语门禁。配一个 rule pack,列出禁用词(
login→sign in)、永不翻译的品牌词(Pull Request、Slack)、按 locale 的语气约束(德语营销用敬语Sie,法语营销用tu)。Vale 在每个 PR 上由 pre-commit 调起。违反术语表 = 构建失败。无例外、无软警告。 - LanguageTool —— 语法和文风门禁。跑在译文上,不是源文。专抓非母语审稿人永远发现不了的安静 bug —— 德语格、法语性数一致、西语 ser/estar、俄语复数。在你的 CI 集群里自托管成 HTTP API。
- typos —— 拼写门禁。Rust 写的,单个二进制,pre-commit 里跑。专抓
recieve/recieved/seperator这一类在 LLM 译文里幸存的 typo(因为 LLM 训练语料里就有这些 typo)。配上每 locale 的产品名词典就完事。 - markdownlint —— 译文文档的结构门禁。当
README.md在 10 个 locale 出货,你不能让某个 locale 的译文悄悄破坏标题层级、列表缩进对不齐、代码栅栏闭错位置。这三种 markdownlint 都能抓。在 CI 里对每份译过的.md跑一遍。 - pre-commit —— 把四道门禁串起来的编排器。一份
.pre-commit-config.yaml,在 commit 前对每个 staged 文件跑 typos + Vale + LanguageTool + markdownlint,CI 里再跑一次。任一门禁失败 = commit 失败 = PR 失败 = 不回注。这一份文件,把这个 pack 从「一堆我们跑过一次的工具」变成「每个 PR 都守住的流水线」。
它们怎么协同(CI 驱动流水线)
源内容 (po / xliff / json / Android XML / md)
│
▼
┌──── Weblate (或 Tolgee) ─────┐
│ git push 时抽 key │
│ ───────────────────────── │
│ 通过 REST 暴露字符串 │
└──────────────┬───────────────┘
▼
┌──── 翻译脚本 ──────────────┐
│ 对每个字符串: │
│ • 查翻译记忆库 │
│ • 构 prompt (术语+截图) │
│ • 按字符串类型路由: │
│ 营销文案 → OpenAI SDK│
│ UI 批量 → LibreTranslate │
│ 硬语种 → Transformers (微调) │
│ • 写回 Weblate │
└──────────────┬─────────────┘
▼
┌──── pre-commit 门禁 ─────┐
│ Vale (术语) │
│ LanguageTool (语法) │
│ typos (拼写) │
│ markdownlint (.md 结构) │
│ 任一失败 = PR 不过 │
└──────────────┬───────────┘
▼
走 Weblate commit 回注 → git → 构建
门禁那一行是承重墙。没有 pre-commit 编排这四个 checker,术语漂移、语法 bug、拼写 typo、markdown 破坏会从不同路径、在不同的日子悄悄漏到生产。有了它,每个 PR 要么四个都过、要么合不上。
你会遇到的取舍
- OpenAI SDK vs 自托管 Transformers vs LibreTranslate —— 三者在「成本-质量-隐私」三角的不同位置。OpenAI API 上的 LLM 对上下文敏感字符串(营销、错误信息)质量最高,千字符花几分钱;LibreTranslate 在你 VPC 里跑、不要钱,但低资源语种流畅度掉;Transformers 在自己 TM 上微调,是前两条都不灵时的逃生口。生产模式:按字符串类型路由,不按语言。营销 → LLM,批量 UI → LibreTranslate,LibreTranslate 翻不好的语种 → Transformers 微调。
- Weblate vs Tolgee vs SaaS (Lokalise/Crowdin/Phrase) —— SaaS 上手快但锁定 + 按字符串收费。50,000 字符串 × 12 locale,这账算不过来。Weblate 是审稿人活在 PR 里的团队的默认;Tolgee 是审稿人需要 in-context 编辑的团队的默认。只在你真的用得上某些集成、且对账单不在乎时选 SaaS。
- CI 门禁做软警告 vs 硬失败 —— 软警告会被无视。硬失败偶尔会让人崩溃(译文其实是对的,门禁误报)。正解:硬失败 + 文档化的 override 路径 —— 工程师加
# vale-ignore: TermsCheck注释 + code review 写明理由 → PR 放行 → 每周审计 override。永远别把门禁当咨询。 - 翻译记忆库归属 —— 你的 TM 比代码更敏感。里面装着每条发布前的 release note、每条客服回复、每条法律免责。TMS 必须自托管(Weblate 或 Tolgee),且只在脱敏(PII + 未发布内容)之后才把字符串送给第三方 LLM。LibreTranslate + 自托管 Transformers 路径就是为这个场景存在的。
常见踩坑
- 占位符被翻译 —— LLM 热心地把
{username}翻成{nombreusuario},下次渲染崩。打开 Weblate 的占位符检查;翻译脚本里在调 LLM 前锁占位符、调完替换回来。 - prompt 里忘传术语表 —— 自研翻译脚本里最常见的 bug。不传术语表,LLM 每次给 "workspace" 选个不同的词。修复是 prompt 模板里多一行;千万别省。
- 按语言路由而不是按字符串类型 —— 「所有法语走 LLM」听起来便宜,直到你的法语营销文案读起来像机器人写的。按字符串类型路由:营销 → LLM,批量 UI → NMT,与语言无关。
- 把 CI 门禁当咨询 —— 第一个工程师 override 一个 Vale 失败而没走 code review 那刻,门禁就死了。要么硬失败、要么没有。
- 译文文档不跑 markdownlint —— 翻译后的
README.md凌晨两点把文档构建打挂,因为某西语译者把*写成-了。markdownlint 是这套里最便宜的保险,第一个打开。 - 没有人参与的抽样审查 —— 全自动流水线会漂移。每周抽 1% 已合译文进人工审查队列。抽样的指标告诉你哪些门禁要调、哪些 locale 该上 Transformers 微调。
10 个资产打包就绪
常见问题
这个 pack 和译者的多语种栈有什么区别?
操作者不同、框架不同。译者套装是为人类本地化工程师设计的,流水线里有真人译者 —— Weblate、术语表 owner、post-edit 工作流、PDF/视频格式工具。这套是为不希望批量路径里有人的工程团队设计的:pre-commit 在每个 PR 上编排 Vale、LanguageTool、typos 和 markdownlint;openai-python SDK 或自托管 Transformers 跑批量翻译;只有被门禁拒掉的字符串才到人手里。TMS 层一样(Weblate、Tolgee、LibreTranslate 两套里都有,因为它们对两个工作都是对的),但自动化层不一样。
为什么要三个翻译引擎而不是一个?
因为「成本-质量-隐私」三角上没有单一引擎能全占。OpenAI API 的 LLM 是上下文感知的(它知道 {user_name} 是占位符,知道 SaaS 里的 'trial' 是免费试用不是法庭审判),但按 token 收费、且把字符串送到第三方。LibreTranslate 在你 VPC 里免费跑、无速率限制,但低资源语种流畅度掉。Hugging Face Transformers 让你能在自己 TM 上微调 NLLB-200 或 M2M-100、用自家 GPU 部署 —— 这是 LibreTranslate 翻不好且 OpenAI 太贵的语种的逃生口。生产模式按字符串类型路由、不按语言:营销走 LLM,批量 UI 走 LibreTranslate,硬语种走微调的 Transformers。
Vale 和 LanguageTool 真的两个都要进流水线?
要 —— 它们抓的 bug 类不同。LanguageTool 是语法检查器:它知道德语格、法语性数一致、西语 ser/estar、俄语复数等非母语审稿人永远发现不了的事。Vale 是文风和术语 linter:它强制你的术语表(不准说 login、必须说 sign in)、品牌词(不准翻译 Pull Request)、按 locale 的语气约束。LanguageTool 抓语法漂移,Vale 抓政策漂移。只跑一个,另一类 bug 就漏到生产。两个在 CI 里跑都很便宜。
本周能交付的最小可行版本长啥样?
四件套。Weblate(Docker,一个下午对接你的 git 仓库)。LibreTranslate(一个容器,接成 Weblate 的 MT 建议后端)。pre-commit 跑 typos + markdownlint(一份 .pre-commit-config.yaml,十分钟)。再加一份 100 行的翻译脚本,用 openai-python SDK 读 Weblate REST API,对所有 tag 为 'marketing' 的字符串调 LLM(prompt 里带术语表),把结果写回。这就是 v1:批量 NMT 预翻译、营销字符串 LLM、两道 CI 门禁守门。第二周加 Vale + LanguageTool 兜漏。Transformers 微调只在你能证明某个 locale 确实需要时再上。
怎么避免把敏感 TM 送给第三方 LLM?
三层防护。第一,按字符串敏感度路由:在 Weblate 里 tag 为 confidential 或 pre-launch 的,路由到 LibreTranslate 或自托管 Transformers,永远不走 OpenAI API。第二,调 LLM 前跑 PII 脱敏 —— 把用户名、邮箱、客户 ID 换成占位符,翻完再换回来。第三,和 LLM 供应商签 DPA、在安全评审里文档化数据流。这个 pack 把 LibreTranslate 和 Transformers 列在 openai-python 前面就是因为这条:自托管路径是默认、LLM 是上下文为王字符串的逃生口,不是批量大锤。