Tolaria 国际化架构:JSON 语言目录 + Lara CLI 翻译同步(ADR-0087 深度解析)

发布时间:2026/9/13 6:11:39
Tolaria 国际化架构:JSON 语言目录 + Lara CLI 翻译同步(ADR-0087 深度解析) Tolaria 国际化架构JSON 语言目录 Lara CLI 翻译同步ADR-0087 深度解析【免费下载链接】tolariaDesktop app to manage markdown knowledge bases项目地址: https://gitcode.com/GitHub_Trending/to/tolaria本文基于 Tolaria 仓库中的架构决策记录 ADR-0087 展开讲解 Tolaria 如何将 UI 文案的“单一事实来源”从手写 TypeScript 字典迁移到扁平 JSON 语言目录并通过 Lara CLI 实现多语言翻译的自动化工作流。读完本文你将理解 Tolaria 的依赖轻量级 i18n 运行时如何加载目录、做英文回退与插值掌握lara.yaml配置、pnpm l10n:*脚本与scripts/validate-locales.mjs校验门禁的完整用法并能按“数据先行”的流程为 Tolaria 新增或更新一个语言。背景为什么手写 TS 字典不再够用ADR-0087 取代了前一日期的 ADR-0084。回顾这一演进脉络ADR-0084 奠定了应用自持的本地化层Tolaria 在 src/lib/i18n.ts 中实现了一个零依赖的前端 i18n 模块英文为规范回退语言简体中文zh-Hans是第一个额外语言ui_language是安装级设置存于~/.config/com.tolaria.app/settings.jsonnull表示“支持则跟随系统语言否则用英文”缺失的翻译键回退到英文语言偏好通过 props 从App.tsx向下传递遵循项目既有的 props-down 架构不引入全局 React Context。瓶颈出现在扩展期ADR-0087 的 Context 部分指出手写维护的 TypeScript 字典对“第一个本地化 UI 面”够用但难以扩展到更广的语言矩阵也无法支撑机器辅助的翻译工作流。ADR-0087 的核心诉求因此可以概括为三句话支持更多语言、用 Lara CLI 自动化翻译更新、运行时保持依赖轻量且保留英文回退行为。决策总览JSON 目录成为唯一事实来源ADR-0087 的 Decision 部分给出了六条约定它们共同构成了 Tolaria 本地化的内容管理基线保留应用自持的运行时本地化层但翻译的唯一事实来源迁移到 src/lib/locales/ 下的扁平 JSON 目录。en.json是规范源目录canonical source catalog其余语言一律一个语言代码一个文件例如 zh-CN.json、fr-FR.json。src/lib/i18n.ts 继续负责回退、插值、语言解析和 props-down 语言布线只是改为从 JSON 文件加载目录而不是 TypeScript 对象。Lara CLI 配置放在仓库根目录的 lara.yaml翻译执行统一走仓库脚本pnpm l10n:translate与pnpm l10n:translate:force。scripts/validate-locales.mjs 负责校验仓库中每个存在的语言目录都必须与英文键集一致且只包含扁平字符串值。旧版存储的语言偏好如zh-Hans会被规范化为规范语言码zh-CN。被否决的替代方案ADR 还记录了三个被否决的方案理解它们有助于把握设计取舍继续用 TypeScript 字典、让 Lara 指向.ts文件可行但 JSON 是翻译工具链更标准的交换格式且对译者和评审者来说 diff 更干净。立即引入完整的前端 i18n 框架被拒绝因为 Tolaria 已有可用的语言传播与回退行为当下真正需要的是更好的内容管理与翻译自动化而不是引入新的框架面。把译文存放在应用仓库之外被拒绝因为 Tolaria 的界面chrome本地化应当与消费它的代码一起版本化管理。这些决策带来的结果Consequences也很直接译者与自动化工具面对的是纯 JSON 而非源码运行时保留英文回退缺失的语言文件或键不会破坏界面新增语言变成“先数据/配置”的变更流程本地化工作拥有了可以在 CI 或提交前运行的独立校验步骤。仓库中的语言目录现状当前 src/lib/locales/ 目录下共 21 个 JSON 文件与 src/lib/i18n.ts 中导出的APP_LOCALES常量一一对应en, it-IT, fr-FR, de-DE, ru-RU, es-ES, pt-BR, pt-PT, es-419, zh-CN, zh-TW, ja-JP, ko-KR, vi, pl-PL, be-BY, be-Latn, id-ID, uk-UA, sv-SE, sk-SK几个值得注意的点be-Latn白俄罗斯语拉丁转写出现在APP_LOCALES和 locales 目录中但没有列入 lara.yaml 的target列表——从仓库结构看可以推断它是人工维护的目录不走机器翻译流程。英文en.json既是源目录也是运行时类型的基础src/lib/i18n.ts 通过typeof EN_TRANSLATIONS派生出TranslationCatalog与TranslationKey两个类型所有组件用到的 key 都受 TypeScript 类型检查约束。文案 key 采用点分层级命名如status.conflict.count、editor.find.matchCount并内嵌{count}、{plural}之类的插值占位符例如en.json中的status.conflict.count: {count} conflict{plural}——{plural}用于英文复数conflicts在非英文语言中会被置空后文会展开。运行时i18n.ts 如何加载 JSON 目录ADR-0087 声明运行时行为不变、只是换数据源源码印证了这一点核心在 src/lib/i18n.ts 的三处实现目录的构建与加载import EN_TRANSLATIONS from ./locales/en.json // ... const LOCALE_MODULES import.meta.glob(./locales/*.json, { eager: true, import: default }) const TRANSLATIONS buildTranslations()buildTranslations()src/lib/i18n.ts#L235-L251以en为种子再遍历import.meta.glob匹配到的每个*.json用文件名去掉.json后缀解析出语言码后写入翻译表。注意两点eager: true所有目录在构建期被静态打包进 bundle因此新增语言目录不需要任何运行时网络请求这是 Tolaria 桌面端“离线可用”本地化的基础解析出的语言码会再次经过normalizeLocaleCode未识别或为en的文件会被静默跳过。翻译与回退export function translate(locale: AppLocale, key: TranslationKey, values?: TranslationValues): string { const catalog Reflect.get(TRANSLATIONS, locale) as PartialRecordTranslationKey, string | undefined const template Reflect.get(catalog ?? {}, key) as string | undefined const fallbackTemplate Reflect.get(EN_TRANSLATIONS, key) as string return interpolate(template ?? fallbackTemplate, localizedInterpolationValues(locale, values)) }src/lib/i18n.ts#L280-L285 的回退链路是目标语言目录中查不到模板 → 用en.json的模板 → 统一走interpolate。这保证了 ADR 中“缺失语言文件或键不会破坏界面 chrome”的承诺。组件侧则通过createTranslator(locale)src/lib/i18n.ts#L287-L289拿到绑定语言的t函数语言本身由 App.tsx 通过 props 下发符合 ADR-0084 确立的 props-down 约定。插值与复数占位符interpolate()src/lib/i18n.ts#L267-L273用正则/\{(\w)\}/g替换{xxx}占位符未提供的值会保留原文占位符而不是变成空串这是一个刻意的防错设计。另一个细节是localizedInterpolationValues()src/lib/i18n.ts#L275-L278当目标语言不是英文时传入的plural值会被置为空字符串。也就是说英文模板{count} commit{plural} ahead of remote会渲染出3 commits ahead of remote而zh-CN模板则完全不必包含{plural}——但这也意味着所有语言目录的占位符集合必须与英文一致这正是校验脚本要强制检查的见下文。语言码规范化normalizeLocaleCode()src/lib/i18n.ts#L291-L300先做trim、_→-、小写化然后依次查别名表和“唯一前缀匹配”。每个语言在LOCALE_DEFINITIONS中登记了别名例如zh-CN的别名包含zh、zh-cn、zh-hans、zh-sgzh-TW的别名包含zh-tw、zh-hant、zh-hk、zh-moes-419的别名覆盖es-ar、es-mx、es-co等 20 个拉美地区码。这就实现了 ADR 中“遗留存储偏好如zh-Hans被规范化为规范zh-CN语言码”的一条关键路径normalizeUiLanguagePreferencesrc/lib/i18n.ts#L302-L309把任意历史值收拢到当前语言集auto/system则映射为跟随系统的特殊值SYSTEM_UI_LANGUAGE。而resolveEffectiveLocale()src/lib/i18n.ts#L324-L339在此基础上实现了完整的解析优先级显式偏好 浏览器语言列表逐个匹配 英文兜底。src/lib/i18n.test.ts 用测试锁定了这些行为例如expect(normalizeUiLanguagePreference(zh-Hans)).toBe(zh-CN) expect(normalizeUiLanguagePreference(zh-Hant)).toBe(zh-TW) expect(normalizeUiLanguagePreference(auto)).toBe(system) expect(normalizeUiLanguagePreference(xx-ZZ)).toBeNull() expect(serializeUiLanguagePreference(zh-Hans)).toBe(zh-CN)serializeUiLanguagePreference还承担“存储规范化”偏好为“跟随系统”时序列化为null因此旧版本写入的zh-Hans会在下一次保存设置时被就地升级为zh-CN无需迁移脚本。Lara CLI 配置lara.yaml 逐项解读仓库根目录的 lara.yaml 是翻译自动化的全部配置内容如下仓库实际内容version: 1.0.0 project: instruction: Tolaria is a desktop knowledge-management app. Keep product names, CLI names, markdown wikilinks, frontmatter keys, file paths, and placeholders like {agent}, {zoom}, {language}, {name}, {label}, {file}, and {count} unchanged. locales: source: en target: - it-IT - fr-FR - de-DE - ru-RU - es-ES - pt-BR - pt-PT - es-419 - zh-CN - zh-TW - ja-JP - ko-KR - vi - pl-PL - be-BY - id-ID - uk-UA - sv-SE - sk-SK files: json: include: - src/lib/locales/[locale].json exclude: [] lockedKeys: [] ignoredKeys: []各字段的含义与作用字段取值说明project.instruction提示词给机器翻译的指令产品名、CLI 名、wikilink、frontmatter 键、文件路径以及{agent}、{count}等占位符必须原样保留。这与validate-locales.mjs的占位符一致性检查形成“翻译时约束 翻译后校验”的双保险。locales.sourceen源语言对应en.json。locales.target20 个语言码机器翻译的目标语言矩阵与APP_LOCALES相比少be-Latn见前文推断。files.json.includesrc/lib/locales/[locale].jsonLara 扫描并同步的 JSON 文件 glob[locale]是 Lara 对“每语言一个文件”的约定占位。files.json.exclude/lockedKeys/ignoredKeys空当前没有排除文件或锁定/忽略的 key所有 key 均参与翻译流程。配套的 lara.lock 是 Lara 的增量翻译状态文件它为en.json的每个 key 记录源文案的哈希例如status.conflict.count: 46293d20ed2071fb30762b71fdaa5894。翻译时源文案哈希未变的 key 可以直接复用已有译文只有真正改动的 key 才会重新翻译——这也是常规命令与--force命令的差异所在。翻译工作流pnpm 脚本与执行顺序package.json 中注册了三个 l10n 脚本translated/lara-cli作为依赖声明在 devDependencies当前^1.3.2l10n:translate: lara-cli translate, l10n:translate:force: lara-cli translate --force, l10n:validate: node scripts/validate-locales.mjs命令作用适用场景pnpm l10n:translate增量翻译跳过 lara.lock 中哈希未变的 key日常更新文案后的例行同步pnpm l10n:translate:force强制翻译忽略缓存对目标语言全部重跑更换翻译供应商/模型后或怀疑译文质量需要整体重译pnpm l10n:validate运行 scripts/validate-locales.mjs 校验所有目录提交前 / CI 门禁这与 ADR-0087 描述的落地流程完全一致先加语言元数据APP_LOCALES、LOCALE_DEFINITIONS、lara.yaml的 target→ 跑 Lara → 评审 JSON 输出 → 校验 → 发布。新增一个语言时buildTranslations()基于文件名的动态发现意味着只要目录文件存在且文件名是合法语言码运行时就会自动接入无需改任何加载代码。校验门禁validate-locales.mjs 的四重检查scripts/validate-locales.mjs 是一个零依赖的 Node 脚本约 150 行它以en.json为基准对 src/lib/locales/ 中的每个目录执行检查任一失败即以退出码 1 结束并逐条打印问题。四类检查如下扁平结构检查assertFlatStringCatalogscripts/validate-locales.mjs#L50-L66目录必须是普通对象不允许数组且每个 key 的值必须是字符串。en.json本身也在检查范围内——源目录坏了同样报错。键集一致性missingKeys/extraKeysscripts/validate-locales.mjs#L68-L76非英文目录相对英文键集既不能缺 key也不能多 key。缺 key 会被英文回退掩盖而不报错多 key 则意味着死文案或拼错两者都被视为问题。占位符一致性placeholderIssuesscripts/validate-locales.mjs#L91-L108对每个共同 key提取源文案与译文中的{xxx}占位符并排序后逐一比对数量或名称不同即报错例如zh-CN: key status.conflict.count placeholders differ (expected count, plural, found count)这一检查与interpolate保留未知占位符的行为互为补充机器翻译若漏掉{plural}校验会直接拦截而不是留到运行时。汇总输出全部通过时打印Validated N locale catalog(s) against M English keys.方便 CI 日志确认覆盖数量。从脚本实现看它不依赖lara.lock或任何 Lara 状态可以独立于翻译流程随时运行——这正是 ADR Consequences 中“本地化工作拥有可在 CI 或提交前运行的专用校验步骤”的落地形态。实践清单按 ADR-0087 流程更新本地化结合上述源码与配置日常维护 Tolaria 本地化时的完整操作链是改文案在 src/lib/locales/en.json 中新增或修改 keykey 需同步在组件中通过TranslationKey使用TypeScript 会约束拼写。同步翻译运行pnpm l10n:translate仅源文案变化的 key 会被增量翻译需要整体重译时用pnpm l10n:translate:force。评审 diff检查 src/lib/locales/ 下各*.json的变更确认产品名、wikilink、frontmatter 键与{agent}/{count}等占位符未被误译对照 lara.yaml 中的instruction。校验运行pnpm l10n:validate确认没有缺键、多键或占位符漂移。新增语言可选在 src/lib/i18n.ts 的APP_LOCALES与LOCALE_DEFINITIONS中登记语言码、日期语言、标签 key 与别名在 lara.yaml 的target中加入语言码提交 src/lib/locales/xx-XX.json 目录文件随后重复第 24 步。小结ADR-0087 的本质是一次“内容管理”层面的重构而非运行时改造翻译事实来源从 src/lib/i18n.ts 内的 TS 对象下沉为 src/lib/locales/ 的扁平 JSONlara.yaml lara.lock 三个l10n:*脚本构成机器翻译流水线scripts/validate-locales.mjs 作为提交前/CI 的质量门禁而运行时继续保留英文回退、props-down 语言传播与旧偏好码zh-Hans→zh-CN的自动规范化。对读者而言这套方案给出了一个可借鉴的桌面应用本地化模板运行时越薄、数据越平、校验越严语言矩阵扩展的成本就越低。【免费下载链接】tolariaDesktop app to manage markdown knowledge bases项目地址: https://gitcode.com/GitHub_Trending/to/tolaria创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考