news 2026/9/19 14:32:20

如何一次性支持38种语言:Proton WebClients 国际化 i18n 体系完整拆解(ttag 翻译工作流指南)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
如何一次性支持38种语言:Proton WebClients 国际化 i18n 体系完整拆解(ttag 翻译工作流指南)

如何一次性支持38种语言:Proton WebClients 国际化 i18n 体系完整拆解(ttag 翻译工作流指南)

【免费下载链接】WebClientsMonorepo hosting the proton web clients项目地址: https://gitcode.com/gh_mirrors/we/WebClients

Proton WebClients 是 Proton 官方的前端 Monorepo,集中管理了 Mail、Drive、Pass、Calendar、Account 等所有 Web 客户端。它的国际化(i18n)体系用一套轻量方案支撑了38 种语言:开发者在代码里用ttag标记可翻译文本,再用 Monorepo 内置的 packages/i18n/ CLI 工具一键提取、校验翻译文件,最后通过 Crowdin 平台分发给全球翻译者。本文带你从零拆解这套 ttag 翻译工作流的完整链路。

一、i18n 文件布局:每个应用一个 locales 目录

Monorepo 采用"应用自治"策略:每个应用在自己的根目录下维护语言包,而不是堆在一个全局目录里。以 Mail 为例:

路径作用
applications/mail/locales/*.json各语言翻译文件,共 30+ 种语言
applications/mail/locales/config/locales.json语言代码 → 母语名称的映射(如"fr_FR": "Français"),驱动 UI 语言切换器
applications/account/locales/*.jsonAccount 应用的语言包,覆盖 38 种语言(含ar_SAkab_DZ等长尾语种)

打开任意一个语言文件(如 fr_FR.json),能看到统一的三段式结构:

{ "headers": { "language": "fr_FR", "plural-forms": "nplurals=2; plural=(n >= 2);" }, "contexts": { "account": { "Mail forward stopped": ["Le transfert automatique de messages a été arrêté."] } } }
  • headers:语言元信息,重点是plural-forms(复数规则)——德语、俄语、波兰语的复数规则各不相同,这一行让翻译引擎按目标语言的语法规则处理数量词;
  • contexts:按模块分区的键值对。同一个文案在不同模块(如"账户设置" vs"日历邀请")允许有不同的译文,靠 context 命名空间隔离,避免"一词多义"被错误统一。

二、代码中如何标记可翻译文本:ttag 模板字符串

Proton 的 React 项目统一使用 ttag 来标记文本,核心就是一个叫trans的模板字符串:

import { trans } from '@lingui/core'; // 基本用法:模板字符串里的英文即翻译源文本 const msg = trans`Mail forward stopped`; // 带变量:${ name } 会被翻译工具识别为占位符,翻译者不能改动变量名 const msg = trans`{ name } sent you a file`; // 指定 context + 复数形式 const msg = trans({ context: 'account', defaults: { one: '1 other potential leak detected', other: '${ n } other potential leaks detected' }, });

几个关键点:

  1. 源码即模板trans的默认参数就是英文原文,提取工具扫描代码即可生成翻译模板,不需要单独维护 key 字典;
  2. 变量受保护${ name }这类占位符会被校验脚本提取比对,翻译中变量缺失或数量不一致会直接报错;
  3. context 必填:没有 context 的翻译在 CI 校验阶段会被判定为不合规(见下文)。

在组件库里实际调试时,可以借助 Storybook 预览组件的文案渲染效果(组件库位于 packages/components/):

三、proton-i18n CLI:提取与校验的三条命令

packages/i18n/ 是一个 npm 内部包@proton/i18n,暴露proton-i18n命令行,官方说明见 Readme.md:

$ npx proton-i18n help Available commands: - validate <type> # 校验翻译:检查 context、变量、ttag 格式 - extract <type> # 从代码中提取全部翻译到模板文件

1️⃣ extract:如何做到"只提取真正会发布的文案"

提取逻辑见 scripts/extract.sh。它的巧妙之处在于不在源码上提取,而在构建产物上提取

  1. 先用 Webpack 构建出dist包(经过 tree-shaking,被删除的死代码里的文案不会出现);
  2. 用 extract-sourcemaps.mjs 从 sourcemap 还原出实际打进了 bundle 的源码;
  3. 调用 ttag-cli 执行ttag extract,输出 gettext 标准的.pot模板文件。

这样翻译平台上永远不会出现"代码已删除、文案还留着"的僵尸词条,模板文件与线上真实内容严格一致。

2️⃣ validate:三重质量门禁

lib/validate.js 用gettext-parser解析模板文件后执行三层检查:

检查项实现拦截的问题
无 context 检查validateWithoutContext翻译者/开发者漏写 context 导致文案串区
变量匹配检查validateVariables单数/复数句中${ n }占位符数量不一致
重复变量检查validateContextAndVariables同一 context 下两条文案变量结构完全相同却语义不同

任意一项失败都会抛出N translations without context!类错误,直接卡住合并。

3️⃣ lint-functions:ttag 写法规范检查

validate --lint-functions会运行 scripts/linter.mjs,检查源码里trans是否使用了正确格式(比如是否误把函数调用结果传进去、模板字符串是否带 context),把问题拦在代码提交阶段而不是翻译阶段。

四、从代码到 Crowdin:翻译工作流全链路

把上面的环节串起来,就是一条完整的自动化流水线:

  1. 开发者写代码trans标记文案 + context;
  2. CI 跑 lintlint-functions保证 ttag 写法规范;
  3. 构建后提取extract生成po/template.pot
  4. 上传翻译平台:CLI 通过 .env 中的I18N_TEMPLATE_FILE识别模板路径,同步到 Crowdin;
  5. 翻译者协作:各语言译员在 Crowdin 上提交.po译文;
  6. 回写语言包:译文落地为各应用locales/目录下的 JSON(格式即上文第二段);
  7. 合入前校验validate三重门禁确保变量、context 完整。

整个链路里没有任何手工拷贝粘贴的环节,38 种语言 × 10+ 个应用、数以万计的词条,全部靠这套 CLI 保证一致性。

五、如何新增一种语言:三步走

想为某个应用增加新语言(例如vi_VN越南语),步骤非常清晰:

  1. 建文件:在applications/<app>/locales/下新建vi_VN.jsonheaders里填对该语言的plural-forms规则;
  2. 登记语言:在 locales/config/locales.json 中追加"vi_VN": "Tiếng Việt",语言切换器即刻生效;
  3. 同步词条:在 Crowdin 上创建对应项目,跑proton-i18n validate确认无缺失 context 后即可发布。

得益于模板文件自动提取 + CI 校验,新增语言的成本基本只剩"翻译"本身。

总结:为什么这套 i18n 体系值得借鉴

Proton WebClients 的国际化方案没有引入沉重的框架,而是抓住了三件事:ttag模板字符串让"英文原文"成为唯一事实来源(代码、模板、界面永不脱节);基于构建产物提取词条(消灭僵尸文案);用 CLI + CI 把 context、变量校验变成硬性门禁(翻译质量自动化)。对于同样要支持多语言、多应用的 Monorepo 项目,这套 ttag + gettext 的轻量组合是一个可直接抄作业的成熟范式。

  • 核心工具源码:packages/i18n/lib/extract.js、packages/i18n/lib/validate.js
  • 提取脚本:packages/i18n/scripts/extract.sh
  • 语言包示例:applications/mail/locales/fr_FR.json

【免费下载链接】WebClientsMonorepo hosting the proton web clients项目地址: https://gitcode.com/gh_mirrors/we/WebClients

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/19 14:31:23

Linux .sh 脚本实战:从运维夜班到自动化清理与健康检查

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/19 14:31:00

医学影像重采样:空间坐标系重建与HU值保真

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/19 14:30:36

Flutter插件鸿蒙化适配:flutter_iot_wifi WiFi配网功能迁移实战

前阵子把一个智能家居 App 的 Flutter 工程往 OpenHarmony 设备上迁移&#xff0c;页面、状态管理、网络层都还算顺利&#xff0c;卡得最久的反而是一个平时没人注意的插件&#xff1a;flutter_iot_wifi。这个插件干的是 IoT 设备 WiFi 配网里最基础的事——扫描附近热点、读取…

作者头像 李华
网站建设 2026/9/19 14:30:10

Exchange Server 2016部署与DAG高可用实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/19 14:30:08

Unity资源管理与代码热更:YooAsset与HybridCLR黄金组合实践

1. 为什么要把它俩“焊”在一起做 Unity 客户端开发做到一定阶段&#xff0c;大家基本都会撞上两堵墙&#xff1a;一是资源包体越堆越大、加载流程越来越乱&#xff1b;二是线上玩法出 Bug 想紧急修&#xff0c;却只能干等审核和整包更新。单看这两个问题&#xff0c;业界都已经…

作者头像 李华
网站建设 2026/9/19 14:29:04

BMS电池管理系统开发指南:硬件架构、SOC/SOP算法与CAN调试

简介&#xff1a;一份关于电动汽车电池管理系统&#xff08;BMS&#xff09;设计的专业参考文献&#xff0c;面向新能源汽车研发工程师、高校师生及技术爱好者&#xff0c;围绕BMS在整车中的核心作用&#xff0c;系统梳理了电池状态监控、健康诊断、充电管理、温度管理、安全保…

作者头像 李华