ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

TeXlyre 国际化实战:i18next + Crowdin 多语言翻译流水线全解

TeXlyre 国际化实战:i18next + Crowdin 多语言翻译流水线全解

【免费下载链接】texlyre

A local-first LaTeX & Typst web editor with real-time collaboration & offline support

项目地址:https://gitcode.com/gh_mirrors/te/texlyre
点击查看免费下载

TeXlyre 国际化实战:本地优先的 LaTeX & Typst 协作 Web 编辑器 TeXlyre 如何基于 i18next 与 Crowdin,用一套约 2400 行的脚本流水线,支撑 5 种语言、2794 个翻译键的自动提取、覆盖率统计与社区翻译协作?本文带你完整拆解这条多语言翻译流水线的设计思路与落地细节。

为什么要做国际化:一个界面,五种语言

TeXlyre 是一款运行在浏览器里的 LaTeX / Typst 编辑器,面向全球用户。官方目前维护 5 种界面语言,全部集中在 translations/locales/ 目录下:

语言代码翻译覆盖率备注
Englishen100%源语言(基准)
Arabicar95%RTL 从右向左书写
简体中文zh94%zh-CN.json
Deutschde92%
Italianoit91%

这些覆盖率数字不是手工维护的,而是由脚本自动计算后写回 translations/locales.config.json。整个文件共2794 个翻译键——对一款 Web 编辑器来说,这是一个相当不小的国际化体量。

要支撑这个体量的国际化,TeXlyre 的流水线分为三层:运行时(i18next)、构建时脚本(scripts/i18n/)、社区协作(Crowdin)。下面逐层拆解。

运行时:i18next 初始化与语言切换

一个 46 行的初始化文件

运行时核心是 src/i18n.ts,它把 5 个 locale JSON 文件打包进i18next的resources,并做了两件关键配置:

  • lng: 'en'+fallbackLng: 'en':默认英文,缺词时回退英文,保证任何语言下界面不出现"裸 key";
  • 导出一个轻量的t()函数供全项目调用。

值得留意的是一个小细节:简体中文在resources中注册为zh(而非zh-CN),与locales.config.json中的语言代码对齐。

LocaleContext:把"语言"变成响应式状态

src/contexts/LocaleContext.tsx 负责把语言切换变成全局响应式行为:

  1. 从设置项language读取用户选择,调用i18next.changeLanguage();
  2. 同步更新<html>的lang属性;
  3. 处理 RTL:阿拉伯语等从右向左书写的语言,会自动给<html>设置dir="rtl",整个布局镜像翻转;
  4. 派发language-changed自定义事件,供其他组件(如日期、数字格式化)联动刷新。

方向策略也可在设置里手动指定:auto(跟随语言)、ltr或rtl。

语言设置项与覆盖率展示

语言选择 UI 注册在 src/settings/registerLocaleSettings.tsx 中。它直接读取locales.config.json生成下拉选项,标签格式为"原生名 (英文名)"(如简体中文 (Chinese (Simplified))),因此新增语言无需改 UI 代码——配置文件加一行即可出现在下拉菜单里,并自动显示该语言的翻译覆盖率。

翻译文件规范:用英文原句做键

打开 translations/locales/en.json 会看到一个非常规但高效的设计:翻译键就是完整的英文原句,而不是nav.home这类缩写键。

"Save": "Save", "{count} bytes": "{{count}} bytes", "{count} bytes_one": "{{count}} byte", "{count} bytes_other": "{{count}} bytes"

这种"句子即键"的做法有两个好处:

  • 翻译者看到的是自然英文,无需查键值对照表,降低 Crowdin 社区参与门槛;
  • 键永不与界面脱节——代码里的字符串改了什么,键就跟着变,工具链会自动检出差异。

代价是复杂的复数规则。TeXlyre 采用 i18next 的多后缀约定,后缀来自 CLDR 规范:_zero、_one、_two、_few、_many、_other。不同语言需要的形态不同(阿拉伯语需要 6 种,德语只需 2 种),CONTRIBUTING.md 明确要求:变量只支持{count},且所有标签(如<strong />)和占位符({{count}})必须原样保留。

还有一个专为 Crowdin 准备的产物:base-en.json。它由 scripts/i18n/generate-base-plurals.cjs 自动生成,把每个复数键缺失的形态全部补齐成种子条目,让翻译平台上的语言版本可以按形态独立勾选完成度。

构建时:约 2400 行的 i18n 脚本流水线

这是整条流水线最工程化的部分,全部位于 scripts/i18n/,共 15 个模块、约 2400 行 Node.js 代码,统一入口是 scripts/translate-pages.cjs。

1. 提取:AST 扫描而非正则

extract-translations.cjs 基于Babel 解析器对整个src/做 AST 遍历,收集 JSX 文本、placeholder/title/alt/aria-label属性、registerSetting()中的label/description等可翻译字符串,写入en.json。用 AST 而不是正则的好处:

  • 自动忽略 URL、邮箱、纯数字、常量(内置ignorePatterns);
  • 能自动识别files.length这类计数表达式并转换为{count}占位符,生成对应复数键。

2. 回写:把英文硬编码自动替换为 t() 调用

apply-translations.cjs 方向相反:扫描源码中仍是硬编码英文的 JSX 文本,自动改写成t("...")调用,并自动注入import { t } from '@/i18n'(由 import-manager.cjs 处理)。两个贴心的工程细节:

  • 支持--dry-run预览模式;
  • 默认生成.bak备份文件,回写失败不伤源码;
  • 改写后的代码会经 validate-transforms.cjs 二次语法校验。

设置项有独立的 apply-settings-translations.cjs,专门处理registerSetting()调用。

3. 体检:缺失键、复数与覆盖率

npm 命令作用对应脚本
npm run i18n:extract从源码提取英文文案extract-translations.cjs
npm run i18n:preview/i18n:fix预览 / 应用t()改写apply-translations.cjs
npm run i18n:coverage计算各语言覆盖率calculate-coverage.cjs
npm run i18n:missing找出源码用了但 JSON 里没有的键detect-missing-keys.cjs
npm run i18n:validate校验 locale JSON 合法性并定位错误行列validate-json.cjs
npm run i18n:detect检出"应转 i18n 的动态内容"detect-dynamic-content.cjs
npm run i18n:full一键跑完 预览 → 提取 → 覆盖率 → 回写预览—

其中复数审计值得单说:detect-missing-plurals.cjs 使用浏览器原生的Intl.PluralRules(CLDR 数据)判断每种语言需要哪些复数形态,只生成该语言实际需要的形态种子,避免人工判断语言规则出错。npm run build:prod中内置了i18n:coverage,保证每次发版前覆盖率数据都是最新的。

社区协作:Crowdin 如何接入

翻译本身不在代码仓库里发生,而是在Crowdin平台上由社区完成,流程如下:

  1. 仓库发布新版后,维护者把base-en.json(含全部复数形态种子)推送到 Crowdin 作为源文件;
  2. 社区译者按语言认领、在线翻译,标签与{{count}}变量必须原样保留;
  3. 每个版本发布时,人工把 Crowdin 上的译文手动同步回translations/locales/ 对应的 JSON 文件;
  4. 本地运行npm run i18n:coverage更新locales.config.json,设置界面里的覆盖率即随之刷新。

选择"发布时同步"而非 CI 自动拉取,配合i18n:validate等本地校验脚本,能在合并前拦住格式错误的译文文件。

如何给 TeXlyre 添加一种新语言

依据 CONTRIBUTING.md 的官方指引,只需四步,完全不需要动 UI 组件:

  1. 在 translations/locales/ 新建xx.json(可从en.json复制骨架);
  2. 在 src/i18n.ts 中import并注册到resources;
  3. 在 translations/locales.config.json 的languages数组加一条配置:code、name、nativeName、direction(RTL 语言填"rtl")、filePath;
  4. 运行npm run i18n:full完成排序、校验与覆盖率计算。

完成后,新语言会自动出现在设置面板的语言下拉框中,并带 0% 覆盖率的实时进度。

小结:可复用的国际化工程清单

TeXlyre 的国际化实践浓缩成一条可复用清单:

  • 句子即键:翻译键用英文原句,平台协作零门槛;
  • AST 驱动:提取与回写都基于 Babel 遍历,杜绝正则的误伤与漏网;
  • CLDR 兜底:复数形态交给Intl.PluralRules,不靠人脑记语言规则;
  • 覆盖率即数据:locales.config.json让翻译进度可见、可比、可发版;
  • 平台 + 仓库双轨:Crowdin 管翻译过程,i18n:validate/i18n:coverage管数据质量。

无论项目用什么前端框架,只要把"提取 → 回写 → 校验 → 覆盖率"四件事脚本化,就能以 2794 个键的量级依然保持多语言维护不失控。

【免费下载链接】texlyre

A local-first LaTeX & Typst web editor with real-time collaboration & offline support

项目地址:https://gitcode.com/gh_mirrors/te/texlyre
点击查看免费下载

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进