Motrix 国际化工程:locale 目录、i18next 资源契约与 check:i18n 全量校验
Motrix 国际化工程locale 目录、i18next 资源契约与 check:i18n 全量校验【免费下载链接】MotrixA full-featured download manager.项目地址: https://gitcode.com/GitHub_Trending/mo/Motrix本篇基于 Motrix 仓库中的 i18n 工程契约文档.claude/rules/i18n.md展开讲清一个多语言 Electron 应用如何做到一套资源、三端复用、校验兜底渲染进程、Electron 主进程与 server 端 operator CLI 如何共享同一份 locale 资源en-US.json作为 key 形状真源的完整资源契约字符串叶子、占位符一致性、复数类别以及pnpm run check:i18n背后的校验脚本如何实现目录/文件对齐、逻辑 key 覆盖、复数类别与占位符对等的全量检查。读完本文你可以在 Motrix 中正确地新增语言、命名翻译 key、组织复数变体并在提交前用校验脚本确认没有破坏任何 locale 契约。三条运行时入口一份共享资源Motrix 的国际化铁律是所有用户可见的应用或 operator 字符串必须走 i18next禁止硬编码 UI 标签、消息、通知、对话框文案和 CLI 输出。同一份翻译资源被三条运行时链路消费各自持有独立的 i18next 实例但都只从 src/shared/i18n-resources.ts 这一个静态注册点取数import type { SupportedLocale } from shared/constants/locales import enUS from shared/locales/en-US.json import zhCN from shared/locales/zh-CN.json import zhTW from shared/locales/zh-TW.json export const I18N_RESOURCES { en-US: { translation: enUS }, zh-CN: { translation: zhCN }, zh-TW: { translation: zhTW }, } satisfies RecordSupportedLocale, { translation: Recordstring, unknown }这里的satisfies RecordSupportedLocale, ...是关键设计资源注册表必须穷尽catalog 中每一个 locale少注册一个语言TypeScript 编译直接失败。仓库当前注册了三个语言文件en-US.json、zh-CN.json、zh-TW.json。三条链路各自的使用方式渲染进程React组件通过react-i18next取翻译。初始化在 src/renderer/lib/i18n.ts 中完成除标准的i18n.use(initReactI18next).init(...)外还提供两个工程化能力applyRendererLocale(locale)先用resolveSupportedLocale把任意候选解析为内置 locale再changeLanguageapplyDocumentLocaleMetadata把 catalog 中每个 locale 的code与dir写入document.documentElement的lang/dir属性为 RTL 布局预留了方向位。契约要求在 React 组件中把t放进 hook 的依赖数组保证语言切换后正确重渲染。Electron 主进程直接使用 src/main/lib/i18n.ts 导出的已初始化单例。该文件以resources: I18N_RESOURCES、supportedLngs: SUPPORTED_LOCALE_CODES、lng: DEFAULT_LOCALE、fallbackLng: FALLBACK_LOCALE初始化并设置interpolation: { escapeValue: false }与 React 侧一致避免 HTML 实体二次转义。Server 端 operator CLI不共享主进程单例而是在 src/server/operator-admin.ts 中用i18next的createInstance()从同一份I18N_RESOURCES创建一个隔离实例专门翻译motrix-admin的 CLI 输出如 pairing 审批命令的成功/失败消息。这种隔离避免了 CLI 子进程与服务器运行时之间的实例污染。Locale 目录SUPPORTED_LOCALES 与解析算法所有语言相关行为的真源是 src/shared/constants/locales.ts。注释明确写道资源注册、语言选择器、运行时校验和 locale 测试全部从这份 catalog 派生新增语言从这里开始随后由 TypeScript 与pnpm run check:i18n强制执行。catalog 的结构是一个不可变的LocaleDefinition数组export const SUPPORTED_LOCALES [ { code: en-US, nativeName: English, dir: ltr }, { code: zh-CN, nativeName: 简体中文, dir: ltr }, { code: zh-TW, nativeName: 繁體中文, dir: ltr }, ] as const satisfies readonly LocaleDefinition[]由它派生出SupportedLocale类型联合类型en-US | zh-CN | zh-TWSUPPORTED_LOCALE_CODES只含 code 的只读元组用于 i18next 的supportedLngsDEFAULT_LOCALE en-US与FALLBACK_LOCALE DEFAULT_LOCALE即英文同时是默认语言与回退语言。目录中还有两个值得展开的工具函数canonicalizeLocale负责把外部输入规范化为 BCP-47 标签。它会去除末尾的/.变体后缀、把_替换为-、识别auto/system/c/posix等哨兵值并返回null最后用Intl.getCanonicalLocales做权威规范化。这意味着 Windows 风格写法zh_CN或带变体的en-US-u-...都能被正确处理。resolveSupportedLocale(...candidates)实现了一个明确的多级解析策略注释与代码共同约定了优先级精确 BCP-47 匹配胜出否则优先同语言候选在同语言内再优先同文字体系script否则按 catalog 顺序取第一个最终回退到DEFAULT_LOCALE。逐候选遍历全部落空才走默认值。注意resolveSupportedLocale是应用侧解析函数——它在应用 locale 与 registry listing locale 之间划了一条明确边界见下文市场 locale 独立性一节。资源契约en-US.json 是 key 形状的真源契约文档对翻译资源提出了五条硬性约束每一条都在校验脚本 scripts/check-i18n.mjs 中有对应的检查逻辑key 形状真源src/shared/locales/en-US.json定义所有逻辑 key 的集合。新增任何一个逻辑 key 时必须同时加入SUPPORTED_LOCALES注册的每一个 locale只更新部分语言会被视为错误。穷尽注册src/shared/i18n-resources.ts静态注册这些 JSON 文件新增语言需要三处齐备——src/shared/constants/locales.ts的 catalog 条目、对应的 JSON 资源文件、I18N_RESOURCES的注册项缺任何一处都会被 TypeScript 或check:i18n拦截。叶子必须是字符串每一个翻译叶子leaf都必须是字符串locale 文件保持可读的 UTF-8避免不必要的\uXXXX转义。占位符集合跨语言一致{{placeholder}}集合必须在每个 locale、每个复数变体中保持完全一致。校验脚本的占位符正则\{\{\s*-?\s*([A-Za-z0-9_.-])(?:\s*,[^{}]*)?\s*\}\}刻意覆盖了 i18next 的三种插值形态普通{{ value }}、带转义前缀的{{- value }}、带格式的{{value, format}}比较的是变量名而非字面文本。复数 key 使用 i18next 类别后缀提供Intl.PluralRules(locale)返回的全部 cardinal 类别_zero是唯一允许的额外类别——因为 i18next 把_zero当作精确零值覆盖处理即使某语言的 PluralRules 不含zero类别也合法脚本中PLURAL_CATEGORY_EXTENSIONS new Set([zero])正是这个契约的代码体现。以 en-US.json 中的 operator CLI 文案为例可以看到占位符契约的实际形态row: {{userCode}} {{clientName}} {{clientVersion}} expires in {{ttl}}, approve: Approved {{userCode}} for {{clientName}} ({{clientVersion}}).zh-CN.json、zh-TW.json中对应条目必须包含同一组{userCode, clientName, clientVersion, ttl}变量否则校验失败。另外脚本在构建逻辑族logical family时会禁止一种常见事故同一个逻辑 key 不能既有 base key 又有复数后缀变体二者必须二选一错误信息为mixes a base key with plural variants。命名空间与 key 命名规范契约要求 key 跟随其所属功能模块组织叶子使用 camelCase描述性文案通常以namenameDesc成对出现。文档列出了四组核心命名空间功能域命名空间前缀设置页网格卡片settings.cards.card.*设置对话框/页面settings.dialog.*BitTorrent 设置与 tracker 默认值settings.bittorrent.*Tracker 管理trackers.effective.*、trackers.blacklist.*、trackers.combobox.*、trackers.sync.*最后一条尤其重要Tracker 管理 UI 虽然可配置但不得因为可配置就挪到settings.*下——应扩展既有命名空间而不是发明平行拼写或错位归属。从en-US.json的结构也可以看到这种按功能聚合的层级风格operatorUnlock.*、operatorAdmin.errors.server.*、common.*等顶级命名空间各管一摊便于按模块分工翻译与审查。市场 locale 独立性两套互不约束的契约这是契约文档中最容易被忽略的一条边界应用 locale 与 registry listing locale 是两套独立契约。具体约束SupportedLocale即内置的en-US/zh-CN/zh-TW不得约束 registry 中 listing 的defaultLocale也不得约束 registrylisting.localizations这个开放的 BCP-47 map 里的任何 key——插件市场条目可以声明任意语言的本地化文本市场条目的展示文案与搜索文本必须用 registry 自带的解析器vendored registry resolver解决禁止对 listing locale 使用resolveSupportedLocale也禁止做同族地区推断如zh-TW - zh-CN——市场侧找不到某语言的本地化时不回退到隔壁语言这是与解析函数resolveSupportedLocale的多级回退策略刻意相反的行为。从源码结构看这条边界体现在src/core/plugin/registry/的测试与实现中对defaultLocale/本地化 map 的独立处理与应用侧resolveSupportedLocale的候选回退逻辑完全分轨。LocaleCoordinator串行化、防过时、可补齐的语言切换应用级host-wide的 locale 变更统一走 src/core/i18n/locale-coordinator.ts。这个协调器解决的问题是多个目标渲染窗口、主进程单例、持久化设置对同一次语言切换的反应有先后、有快慢、可能失败裸调changeLanguage会产生竞态与旧值覆盖新值的乱序。它的契约行为——串行化变更、跳过过时修订、补齐迟到的目标、仅在当前应用成功后才发布LocaleChanged——在代码中一一对应export class LocaleCoordinator { private desiredLocale: SupportedLocale private tail: Promisevoid Promise.resolve() private revision 0 private appliedRevision -1 // ... update(locale: SupportedLocale, emitChange: boolean): Promisevoid { this.desiredLocale locale this.revision 1 const revision this.revision return this.enqueue(locale, revision, emitChange) } }串行化所有applyLocale调用通过this.tailPromise 链排队绝不并发执行跳过过时修订applyIfCurrent内部用isCurrent () revision this.revision双重校验应用前查一次applyLocale返回后再查一次用户快速连切语言时旧请求在任一环节发现 revision 落后即静默放弃避免旧语言写回补齐迟到目标reconcile()某些目标在协调器启动后才就绪reconcile在 tail 链上重放最新期望 locale并以appliedRevision this.revision判定收敛——期间若又来了新修订就再重放一次直到追平LocaleChanged只在应用成功后发布enqueue中仅当applied emitChange才调用emitLocaleChanged订阅者收到事件时语言已真实生效失败不堵队列this.tail pending.catch(() {})保证某次切换失败时错误只报告给该次调用的发起方return pending保留了原始 rejection后续的语言变更仍能修复运行时状态。契约文档还要求plugin-registry 与 capability-host 的 locale 变更保持事务性失败时必须回滚。从src/core/plugin/下 capabilities 与 plugin-registry 的测试文件如plugin-registry.overlay.test.ts、plugin-registry.test.ts覆盖的 overlay/回滚路径可以推断这条事务性契约由这些测试守护。验证pnpm run check:i18n任何涉及 locale 文件、catalog、占位符、复数变体或资源注册的改动之后必须运行pnpm run check:i18npackage.json 中该脚本定义为node --import tsx scripts/check-i18n.mjs即由 scripts/check-i18n.mjs 实现。它同时是一个可复用库导出checkI18n、flattenScalarKeys、extractPlaceholders和一个 CLICLI 支持Usage: check-i18n [--catalog-module path] [--locales-dir path]--catalog-modulelocale 目录模块路径默认src/shared/constants/locales.ts--locales-dirlocale JSON 目录默认src/shared/locales。checkI18n接收{ catalog, fallbackLocale, localesDirectory }把SUPPORTED_LOCALES与FALLBACK_LOCALE从目录模块动态导入后逐项校验共覆盖五类问题文件/catalog 对齐catalog 中每个 code 必须有对应code.json文件目录下每个.json文件也必须注册在 catalog 中双向检查防止孤儿文件与缺失文件逻辑 key 覆盖以fallbackLocale ?? catalog[0]即en-US为参照 locale逐 locale 比较逻辑 key 集合精确报告missing .../extra ...超过 12 个时折叠为and N more复数类别对每个复数族用Intl.PluralRules(locale, { type: cardinal }).resolvedOptions().pluralCategories计算该 locale 必须提供的类别缺类别、含非法类别都会报错并直接给出应补/应删的完整 key 名如xxx_one is missing允许类别 必需类别 ∪{zero}字符串叶子任何非字符串叶子含null、数字按 key 精确报错错误信息附带实际收到的类型占位符对等同一逻辑族内部base 与各复数变体之间、跨 locale 之间逐组比较占位符集合报告missing/extra的具体变量名同时强制复数/标量形态与参照 locale 一致must be a plural family to match en-US类错误。成功时输出形如check:i18n passed: 3 locales, N logical translation keys.其中 N 即参照 locale 的逻辑 key 总数可直接用于回归对比。测试侧有两条纪律同样写进了契约文档并能在 tests/scripts/check-i18n.test.ts 中得到印证校验器的测试必须使用临时 fixture该测试在临时目录中生成 JSON 文件甚至动态生成一个自己的SUPPORTED_LOCALES目录模块来喂给checkI18n绝不允许改写真实的 locale 文件需要渲染翻译组件的测试加载真实渲染进程初始化入口import renderer/lib/i18n这样组件测试拿到的是与生产完全一致的 i18next 资源与配置而不是手工拼装的 mock。小结新增一个语言的完整动作清单把契约收敛成一条可执行路径在 src/shared/constants/locales.ts 的SUPPORTED_LOCALES中追加{ code, nativeName, dir }条目 → 新建src/shared/locales/code.json以en-US.json为模板补齐全部逻辑 key含全部复数后缀与相同占位符集合→ 在 src/shared/i18n-resources.ts 的I18N_RESOURCES中注册该文件 → 运行pnpm run check:i18n直到输出passed。任何一步漏掉TypeScript 的satisfies约束或校验脚本都会给出带具体 key 名的错误把翻译漏项这类问题挡在提交之前。【免费下载链接】MotrixA full-featured download manager.项目地址: https://gitcode.com/GitHub_Trending/mo/Motrix创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考