工作流完全指南:从 Key 设计到类型校验与 CI 落地)
AionUi 国际化i18n工作流完全指南从 Key 设计到类型校验与 CI 落地【免费下载链接】AionUiOpen-source 24/7 Cowork app for OpenClaw, Hermes, Claude Code, Codex, OpenCode and 20 more CLI Agent | Customize your assistants | Team them upStar if you like it!项目地址: https://gitcode.com/GitHub_Trending/ai/AionUi本文基于 AionUi 仓库中i18nSkill 规范系统讲解该项目多语言体系的完整工作流以i18n-config.json为单一事实来源的语言/模块配置、命名空间 Key 规范、新增文本与新增模块的标准步骤、硬编码字符串检测红线、zh-TW 术语维护以及i18n:types类型生成与check-i18n.jsCI 校验的底层实现。读完本文你将掌握在 AionUi支持 13 种语言、19 个翻译模块的桌面 WebUI 应用中安全新增用户可见文本、保证所有语言同步且通过 CI 校验的完整实战能力。一、核心理念所有用户可见文本必须走 i18nAionUi 的 i18n 工作流确立了一条铁律所有用户可见文本必须使用 i18n。无论是按钮文案、占位提示、状态消息还是空状态说明都不允许以硬编码形式直接写进组件。Announce at start:Im using i18n skill to ensure proper internationalization.这条规范由scripts/check-i18n.js在 CI / pre-commit 阶段强制兜底详见第八节任何遗漏都会在提交前被拦截。二、单一事实来源先读i18n-config.json进行任何 i18n 工作之前必须先读取src/common/config/i18n-config.json仓库根目录实际路径为packages/desktop/src/common/config/i18n-config.json获取当前支持的语言与模块列表。永远不要假设语言或模块数量是固定的——自本 Skill 编写之后语言和模块可能已被增删。cat packages/desktop/src/common/config/i18n-config.json该文件是唯一事实来源single source of truth所有脚本、运行时代码与本工作流都依赖它。当前仓库中的实际配置如下{ referenceLanguage: en-US, fallbackLanguage: en-US, supportedLanguages: [ zh-CN, en-US, ja-JP, zh-TW, ko-KR, tr-TR, ru-RU, uk-UA, pt-BR, de-DE, es-ES, fr-FR, fa-IR ], modules: [ common, agentMode, update, login, fileSelection, preview, conversation, settings, messages, mcp, acp, codex, tools, google, cron, guid, agent, team, pet ] }三个关键字段的含义字段当前值说明referenceLanguageen-US参考语言类型生成与 Key 基线都以它为基准fallbackLanguageen-US回退语言运行时缺失的翻译自动回退到它supportedLanguages13 种语言每种语言必须拥有完整的模块目录modules19 个模块每个模块对应一种语言的module.json从配置可见AionUi 当前支持简体中文、英文、日文、繁体中文、韩文、土耳其文、俄文、乌克兰文、巴西葡萄牙文、德文、西班牙文、法文和波斯文其中fa-IR为从右向左书写的 RTL 语言运行时需要特殊处理见第九节。三、文件结构模块化 locale 目录packages/desktop/src/renderer/services/i18n/ ├── index.ts # i18next 配置 ├── i18n-keys.d.ts # AUTO-GENERATED — 禁止手动编辑 └── locales/ ├── lang/ # 每种语言一个目录与 i18n-config.json 一致 │ ├── index.ts # Barrel import聚合导出所有模块 │ ├── common.json # 每个模块一个 JSON 文件 │ ├── conversation.json │ └── ... └── ...要点i18n-keys.d.ts是自动生成的由scripts/generate-i18n-types.js从en-US参考 locale 生成严禁手动编辑生成细节见第七节每种语言的index.ts负责以 barrel 方式导入并导出该语言的全部模块 JSON。以zh-CN/index.ts为例它依次import common from ./common.json、import conversation from ./conversation.json…… 共 19 个模块然后统一export default { common, agentMode, ..., pet }语言的目录名、模块 JSON 文件名必须与i18n-config.json完全一致否则check-i18n.js会直接报错见第八节checkDirectoryStructure。四、Key 设计规范命名空间点分与命名规则代码中统一使用命名空间点分namespaced dot notationt(module.key)或t(module.nested.key)。每个模块 JSON 内部Key 可以是扁平或嵌套的// common.json — 扁平 Key { send: Send, cancel: Cancel, copySuccess: Copied } // cron.json — 嵌套 Key { scheduledTasks: Scheduled Tasks, status: { active: Active, paused: Paused } }代码中对应t(common.send); // common.json 中的扁平 Key t(cron.status.active); // cron.json 中的嵌套 Key命名规则Key 使用camelCase如copySuccess、scheduledTasks相关 Key 用嵌套分组如status.active、actions.pause通用文本放common.jsonsave、cancel、delete、confirm 等跨功能复用的词条功能专属文本放对应模块如对话相关放conversation.json定时任务相关放cron.json。常见后缀约定后缀用途title区块/页面标题placeholder输入框占位提示label表单标签success/error状态提示消息confirm确认对话框文案empty空状态消息tooltip悬浮提示文本五、添加新文本的六步工作流Step 1先读i18n-config.json获取当前语言列表与模块列表此步骤不可跳过。理由很简单语言可能新增或移除固定假设会让新 Key 漏加到部分语言。Step 2检查已有 Key优先复用添加新 Key 前先在参考语言目录中搜索是否存在语义相近的 Keygrep -r keyword packages/desktop/src/renderer/services/i18n/locales/en-US/能复用common.*的 Key 就复用避免重复翻译与膨胀。Step 3选择正确的模块按功能区域匹配合适模块若没有合适模块考虑是否需要新增模块见第六节。Step 4添加到所有语言目录最关键一步每个新 Key 必须添加到supportedLanguages中的每一种语言。核对清单en-US/module.json— 参考语言Step 3 已添加zh-CN/module.json— 已添加zh-TW/module.json— 已添加i18n-config.json→supportedLanguages中列出的其他所有语言— 已添加⚠️任一语言缺少该 Key都会导致 CI 中node scripts/check-i18n.js失败。当前仓库对缺少 Key 的行为是输出⚠️警告checkTranslationKeys但对目录缺失、类型不同步等硬性错误会直接以❌失败退出详见第八节。Step 5在组件中使用import { useTranslation } from react-i18next; function MyComponent() { const { t } useTranslation(); return button{t(common.save)}/button; }Step 6重新生成类型并校验顺序不可颠倒提交前必须按顺序执行以下两条命令且两者都必须通过bun run i18n:types # Step A从参考语言重新生成 i18n-keys.d.ts node scripts/check-i18n.js # Step B校验结构、Key 一致性、类型同步i18n:types必须先于check-i18n.js执行——校验脚本会验证生成的类型文件是否与参考 locale 同步若check-i18n.js以错误❌退出修复后才能继续若仅以警告⚠️退出可以评审后继续绝不带着过期的i18n-keys.d.ts提交。在package.json中i18n:types被定义为node scripts/generate-i18n-types.js。六、新增模块的完整流程当功能区域足够独立、现有模块无法容纳时按以下顺序操作将模块名加入src/common/config/i18n-config.json→modules数组在每一种语言目录中创建module.json具体语言数读supportedLanguages在每种语言的index.ts中添加该模块的import与export运行bun run i18n:types重新生成类型定义运行node scripts/check-i18n.js校验。第 3 步是常见遗漏点只添加模块 JSON 却不更新index.ts运行时该模块永远不会被加载checkIndexConfig之外的目录校验也会因模块文件缺失而失败。七、类型生成原理generate-i18n-types.jsscripts/generate-i18n-types.js负责从参考语言en-US生成强类型声明。核心逻辑读取i18n-config.json取referenceLanguage与modules作为REQUIRED_MODULES遍历每个模块的en-US/module.json用getAllKeys递归展开嵌套 Key拼接为module.key形式的完整 Key 集合生成i18n-keys.d.ts导出两个联合类型I18nKey所有common.send、cron.status.active形式的合法 Key 联合类型提供编译期 Key 校验——写错 Key 名会直接报 TS 错误I18nModule19 个合法模块名的联合类型若某个参考模块文件缺失直接throw new Error保证类型生成绝不基于不完整数据生成后依次尝试prettier与oxfmt格式化bunx prettier --write若存在node_modules/.bin/oxfmt则进一步用 oxfmt 统一格式。文件头部写有AUTO-GENERATED FILE - DO NOT EDIT注释任何手动编辑都会在下一轮校验中被标记为过期。八、CI 校验的六项检查check-i18n.js源码剖析scripts/check-i18n.js是整个 i18n 质量体系的守门人由 pre-commit 钩子调用。它依次执行六项检查任何一项产生❌错误都会以退出码 1 终止提交1.checkDirectoryStructure— 目录与文件结构遍历supportedLanguages验证每种语言目录存在否则报错验证REQUIRED_MODULES来自i18n-config.json中每个模块的lang/module.json存在并做JSON.parse语法校验检查每种语言的index.ts存在缺失仅警告额外检查遗留的单 JSON 文件若存在旧的lang.json如zh-CN.json直接报错要求删除——这保证了仓库已彻底迁移到按模块拆分的新结构。2.checkTranslationKeys— 跨语言 Key 一致性以en-US为基线递归收集每个模块的全部 Key然后对其它每种语言逐一对比缺失 Key 输出⚠️警告并统计缺失百分比。这是新 Key 必须加到所有语言这条规则在脚本层面的落实。3.checkEmptyTranslations— 空翻译检测遍历所有语言的所有模块用collectEmptyValuePaths递归找出值为空字符串的 Key 路径并检测整个模块为空文件的情况均输出警告。4.checkLiteralKeyUsages— 代码中字面量 Key 的合法性扫描packages/desktop/src/renderer下所有.ts/.tsx文件跳过node_modules、dist、out与i18n-keys.d.ts剥离注释后用正则\b(?:i18n\.)?t\(\s*([\])([^])\1匹配t(...)形式的字面量调用并与参考 Key 集合比对输出未知 Key 警告。该检查会自动跳过模板字符串插值${与http:///https:// 开头的字符串。5.checkI18nTypeDefinitionInSync— 类型文件同步硬性错误读取i18n-keys.d.ts用正则提取I18nKey与I18nModule联合类型中的全部取值与collectReferenceKeys()得到的预期集合做集合级比对。只要新旧集合不完全相同isSameSet严格比较大小与元素即报❌ Outdated i18n key type file错误并提示重新运行bun run i18n:types。这正是 Step 6 必须先生成类型、再校验的代码依据。6.checkIndexConfig— 运行时配置完整性读取index.ts要求其中必须包含对i18n-config.json的引用、必须导出supportedLanguages并建议支持懒加载loadLocaleModules或动态import否则给出警告。所有检查结束后输出汇总有错误则process.exit(1)阻断提交仅警告则放行。九、运行时加载原理index.ts源码解读packages/desktop/src/renderer/services/i18n/index.ts是 i18next 运行时配置的核心有几个值得注意的设计静态导入 懒加载缓存所有 13 种语言均通过静态import打包import enUS from ./locales/en-US/index等保证打包后的应用可随时切换语言。loadedTranslationsMap 作为翻译缓存回退语言en-US在模块加载时即预置进缓存。语言选择优先级与回退getInitialLanguage()依据三种提示源决定初始语言localStorage 中的i18nextLng、Electron 注入的window.__initialLanguage以及仅在后端启动失败场景下navigator.language。注释特别说明项目刻意不使用i18next-browser-languagedetector——在 WebUI 模式下浏览器 localStorage 与 Electron renderer 的 origin 不同检测器会读到错误值而回退到navigator.language造成语言不一致Issue #1176 的教训。configService 是最终权威初始化完成后initLanguage()等待configService.whenReady()以配置服务中的language字段为最终权威值ensureAndSwitch并把规范化后的语言写回 localStorage 作为下次加载的快速提示。changeLanguage()同样会同步configService.set(language, ...)保证语言选择持久化。桌面与 WebUI 实时同步通过ipcBridge.systemSettings.languageChanged监听主进程广播的语言变更事件来自其它 renderer一旦触发即ensureAndSwitch切换语言并更新 localStorage——桌面端与 WebUI 之间改语言无需重启即可实时联动同时changeLanguage()也会通过ipcBridge.systemSettings.changeLanguage.invoke通知主进程用于托盘菜单等。RTL 与文档方向applyDocumentDirection(lang)保持html dir/html lang与当前语言一致——因为fa-IR波斯语是 RTL 语言切换后必须翻转文档方向。languageChanged事件触发时执行一次初始化时也执行一次。回退合并非参考语言通过mergeWithFallback(fallbackLocale, modules)与en-US合并确保即使某种语言个别 Key 缺失暂未翻译运行时也能回退到英文不会出现空白文案。十、zh-TW 繁体维护大部分繁体词条可由简体自动转换但部分术语需要人工审校。仓库中zh-TW/common.json已落实这些差异例如 send→發送、save→儲存、delete→刪除、confirm→確認、file→檔案、copySuccess→已複製。zh-CNzh-TW说明视频影片术语不同软件軟體术语不同信息訊息术语不同默认預設术语不同新增繁体翻译时遇到这类高频术语务必人工确认不要依赖机械转换。十一、插值与富文本t()与Trans组件变量插值JSON 中定义占位符{ taskCount: {{count}} task(s), greeting: Hello, {{name}}! }代码中传参t(cron.taskCount, { count: 5 });带 HTML 的复杂文案需要富文本如加粗、内联元素时使用Trans组件而不是在翻译字符串里拼 HTMLimport { Trans } from react-i18next; Trans i18nKeycron.countdown Task strong{{ taskName }}/strong in span{{ countdown }}/span /Trans;十二、硬编码字符串的红线与例外以下写法绝对禁止出现在 JSX 中无论中文还是英文// Bad — 硬编码 span重命名/span spanDelete/span {name || 新对话} // Good — 全部走 t() span{t(common.rename)}/span span{t(common.delete)}/span {name || t(conversation.newConversation)}允许的例外代码注释任何语言均可console.log()等调试输出不展示给用户的内部字符串常量。十三、提交前的快速核对清单每次提交包含新文本的代码前逐项确认已读取src/common/config/i18n-config.json拿到当前语言与模块列表所有用户可见文本均使用t()函数新 Key 已添加到supportedLanguages中的每一种语言目录JSX 中无硬编码中文/英文zh-TW 已针对术语差异人工审校先运行bun run i18n:types重新生成i18n-keys.d.ts类型生成后node scripts/check-i18n.js通过无错误。十四、常见错误速查错误做法正确做法假设语言数量固定不变每次先读i18n-config.json只把 Key 加到部分语言加到supportedLanguages的每一种语言手动编辑i18n-keys.d.ts运行bun run i18n:types自动生成使用t(New Chat)这类文案当 Key定义语义化 Keyt(conversation.newChat)新增模块却不更新i18n-config.json先更新配置再创建文件只添加模块 JSON 不更新index.ts必须在每种语言的index.ts中添加 import export结语AionUi 的 i18n 体系是一套配置驱动 类型约束 CI 兜底的完整闭环i18n-config.json定义事实generate-i18n-types.js从参考语言生成I18nKey联合类型把 Key 错误拦截在编译期check-i18n.js的六项检查把结构、一致性、空翻译、类型同步全部卡在提交前而index.ts则通过静态导入、configService 权威语言、桌面/WebUI 实时同步与 RTL 方向管理保证了运行时的体验。遵循本工作流你就能在 13 种语言、19 个模块的规模下安全地演进任何一处界面文案。【免费下载链接】AionUiOpen-source 24/7 Cowork app for OpenClaw, Hermes, Claude Code, Codex, OpenCode and 20 more CLI Agent | Customize your assistants | Team them upStar if you like it!项目地址: https://gitcode.com/GitHub_Trending/ai/AionUi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考