- 桌面应用
【免费下载链接】alt-tab-macos
Windows alt-tab on macOS
本篇技术指南围绕 AltTab(macOS 平台的开源窗口切换器,体验对标 Windows 的 Alt-Tab)仓库中的.claude/skills/translate-missing-l10n/SKILL.md技能文档展开,讲解如何利用 Claude 的多语言能力,从 Swift 源码重新生成本地化字符串、对比 20 个目标语言文件找出缺失键、按规范翻译并借助scripts/l10n/apply_translations.ts原子化合并回写。读完你将掌握一套可复制的"提取—对比—翻译—校验—合并"完整流水线,以及格式说明符校验、en.lproj 对称回写等底层实现细节。
背景:AltTab 的本地化体系
AltTab 是一个帮助用户在 macOS 上像 Windows Alt-Tab 一样切换窗口的应用,其待翻译字符串绝大部分来自设置界面(Settings UI),包括部分 tooltip、对话框和通用用户提示。仓库采用 Apple 标准的Localizable.strings机制,目录结构位于 resources/l10n,每个语言一个.lproj子目录:
resources/l10n/Localizable.strings:由genstrings从 Swift 源码重新生成的源文件(英语,965 行),格式为/* 工程师注释 */ "key" = "value";resources/l10n/<lang>.lproj/Localizable.strings:各目标语言的翻译文件,格式为"key" = "translation";
从 src/Menubar.swift 可以看出源码中的字符串是如何进入本地化体系的——所有用户可见文案都通过NSLocalizedString(_:comment:)包裹,例如NSLocalizedString("About %@", comment: "Menubar option. %@ is AltTab")、NSLocalizedString("Quit %@", comment: "%@ is AltTab"),以及带数字参数的String(format: NSLocalizedString("Trial: %d days remaining", comment: ""), daysRemaining)。comment参数会被genstrings原样写入生成文件的/* */块中,作为翻译时消歧义的工程师指引。
目标语言矩阵(20 种)
技能文档定义了 20 个目标语言代码:
de, ja, fr, es, zh-CN, pt-BR, nl, ko, it, pl, ar, zh-HK, vi, tr, sv, th, zh-TW, he, id, ru其中en是源语言,不需要人工翻译——它在应用阶段由脚本自动处理(详见下文"en.lproj 自动同步")。仓库中实际存在的语言目录与之一一对应:ar.lproj、de.lproj、es.lproj、fr.lproj、he.lproj、id.lproj、it.lproj、ja.lproj、ko.lproj、nl.lproj、pl.lproj、pt-BR.lproj、ru.lproj、sv.lproj、th.lproj、tr.lproj、vi.lproj、zh-CN.lproj、zh-HK.lproj、zh-TW.lproj。
以 resources/l10n/zh-CN.lproj/Localizable.strings 为例,其真实内容形如:
"%@ now has a Pro tier" = "%@ 现已推出 Pro 版本"; "%@ — last seen %@" = "%1$@ — 上次出现于 %2$@"; "3-finger Horizontal Swipe" = "三指水平滑动"; "About %@" = "关于 %@"; "All Spaces" = "所有桌面";翻译硬性准则
SKILL.md 给出了六条必须遵守的翻译规则,任何偏离都会导致译文被工具拒绝或破坏 UI 排版:
- 格式说明符必须原样保留:
%@、%d、%1$@、%2$@、\n、\t的数量和顺序必须与源文一致。带位置索引与不带索引的形式可以互换(%@ %@≡%1$@ %2$@),但数量必须匹配。例如源文"%@ — last seen %@"在 zh-CN 中翻译为"%1$@ — 上次出现于 %2$@"是合法的——说明符数量一致,只是改用了位置索引形式。 - 专有名词不翻译:
AltTab、macOS、Mission Control、Spaces、Dock等保持原文。仓库的 zh-CN 文件也印证了这一点,如"AltTab is a free, open-source window switcher..."中 AltTab 保持原样。 - 修饰键名称不翻译:
Cmd、Option、Alt、Shift、Ctrl、Control、Fn、Command保持原文。 - 优先采用 Apple 官方平台术语:若目标语言在 macOS 系统设置中存在官方术语,应优先采用。例如法语中 macOS 用的是 "Réglages" 而非字面翻译的 "Préférences"。
- 匹配源文的简洁度:设置项字符串通常很短,译文也应保持简洁,优先使用符合语言习惯的短表达,而非逐字逐句的完整语法。
- 匹配 macOS 语气:中性、直接,除非源文本身有感叹号,否则译文不要加感叹号。
- 源文件注释是工程师指引而非用户可见文本:
/* */中的 comment 用于消歧义,绝不进入译文。
完整工作流:七个步骤补全 20 语言缺失翻译
步骤 1:刷新源文件
bash scripts/l10n/extract_l10n_strings.sh该脚本(scripts/l10n/extract_l10n_strings.sh)用genstrings从当前 Swift 代码重新生成resources/l10n/Localizable.strings。其内部实现值得关注:
find src -name '*.swift' | xargs genstrings -a -o resources/l10n:收集 src 下全部 Swift 文件并追加生成;-a表示追加模式。- 用
iconv -f UTF-16LE -t UTF-8将 genstrings 输出的 UTF-16LE 转成 UTF-8。 - 用
sed $'1s/\xef\xbb\xbf//'去掉可能存在的 BOM,保证输出确定性(字节级可复现)。
步骤 2:解析源文件
读取resources/l10n/Localizable.strings,每条记录形如:
/* engineer comment */ "key" = "value";需要构建有序的(comment, key, value)三元组列表。注意:源文件中value通常与key相同,但当存在多个说明符时 value 可能包含位置索引(%1$@、%2$@)。在 apply_translations.ts 中,解析由正则/\/\*\s*([\s\S]+?)\s*\*\/\s*"([\s\S]+?)"\s*=\s*"([\s\S]+?)"\s*;/g完成,同时捕获注释、键与值,并保证按源文件顺序输出。
步骤 3:逐语言计算缺失键
对 20 个目标语言,读取resources/l10n/<lang>.lproj/Localizable.strings。一个键被视为缺失,当且仅当:
- (a) 它存在于源文件中,但在目标文件中不存在;或
- (b) 目标文件中的值为空或仅含空白字符。
情况 (b) 与 (a) 同等处理——必须产出真实译文。这一判定逻辑在脚本中表现为:parseTarget只读取非空文件中匹配到的"key" = "value";条目,空值条目自然不会被合并,等同于缺失。
步骤 4:无缺失则停止
如果所有语言都没有缺失键,报告结果并退出,不产生任何写操作。
步骤 5:翻译(分批进行)
对每个语言、每个缺失键产出译文,严格遵守上文六条准则。不要发明键——只翻译源文件中真实存在的键。为提高单次输出的可管理性,建议每 5~10 个语言分一批进行。
步骤 6:应用批次
将每批结果写到/tmp下的新文件(不入库),JSON 形状为:
{ "fr": { "About %@": "À propos de %@", "Quit": "Quitter" }, "ja": { "About %@": "%@について" } }然后运行:
npx ts-node scripts/l10n/apply_translations.ts /tmp/batch-NN.jsonapply_translations.ts(scripts/l10n/apply_translations.ts)会执行以下关键行为:
- 校验格式说明符:译文与源 value 的说明符集合不匹配时,输出清晰错误并拒绝合并。校验算法
normalizedSpecs先提取%(?:\d+\$)?[@d](即%@、%d、%1$@等,并把%1$@归一化为%@),再提取\n与\t,排序后逐项比对。 - 总是从源键重写
en.lproj/Localizable.strings:每条写为"key" = "key";保持对称,无需在批次中包含en。这样能避免 genstrings 改写出的%1$@形式值泄漏进英文文件(当源键本身用的是普通%@时)。 - 按源顺序重写每个
<lang>.lproj/Localizable.strings:已有翻译保留,新翻译合并进去,已不在源中的键被剪除(pruned)。 - 存在被拒绝条目时以退出码
2结束。
步骤 7:处理拒绝
若apply_translations.ts报告格式说明符不匹配,修正对应译文后,在后续批次中只重跑受影响的语言。在有拒绝未解决之前,不要继续推进。
底层实现剖析:apply_translations.ts 的核心逻辑
理解脚本内部行为,有助于批量翻译时一次通过。其关键实现要点如下:
格式说明符归一化比对(normalizedSpecs/specsEqual):位置索引形式%1$@会被归一化为%@,因此"%@"与"%1$@"等价,但数量必须完全一致;\n、\t被单独作为 token 参与比对,说明译文里换行/制表符的数量和位置也必须与源一致。
en.lproj 自动同步(main 中的 en 分支):脚本无条件从源键重写resources/l10n/en.lproj/Localizable.strings,值为键本身(key == value)。这保证了英文文件永远与当前源码的键集合同步,且不会继承 genstrings 对 value 的位置索引改写。即使批次里出现"en"键,也会被跳过并提示自动同步。
按源顺序合并回写(writeTarget):目标文件不是简单追加,而是以源文件的键顺序为基准逐条写出——已在目标文件中的旧翻译从 Map 中取出保留,新翻译写入,源中已删除的键自动消失。这使每个.lproj文件保持与源一致的稳定顺序,利于 diff 审查。
批次键合法性检查:批次中出现源中不存在的键会被计入rejected并输出key not in source错误,杜绝凭空发明键。
退出码约定:有任何拒绝时进程以2退出,便于 CI 或脚本化流程捕获失败;正常情况为0。
结果报告与收尾
一次完整运行结束后,需要向用户报告:
- 处理的语言数量;
- 产出并合并的译文总数;
- 有意保留不翻译的条目(例如源文本身就是专有名词的情况),列出清单交由用户决策;
- 需要人工修复的格式说明符拒绝,说明修了什么、为什么。
这四类信息中,尤其重要的是"故意未译清单"与"拒绝修复记录"——它们是人工复核与后续回归的基础。
最佳实践小结
- 先刷新、再对比:每次翻译前务必重新执行
extract_l10n_strings.sh,否则新增/删除的键会使缺失判定失真。 - 分批控制规模:每批 5~10 个语言,避免单次输出过大导致质量下降。
- 重视说明符:
%@、%d、\n、\t的复现是整个流程最容易出错、也最容易自动化拦截的环节,翻译完成后可先自查再提交批次。 - 善用 en 自动同步:批次 JSON 永远不需要包含
en,也不要手动修改en.lproj——脚本会以源键为准保持其对称与最新。 - 不通过则不复用:只要存在 rejected,就停下修复并只对受影响语言重跑,保证最终落地文件 100% 通过校验。
这套工作流将"人工翻译判断"与"机器校验合并"明确分工:Claude 负责语言与术语判断,apply_translations.ts负责格式安全与文件一致性,两者结合,使得 AltTab 的 20 语言本地化可以在不破坏格式、不引入无效键的前提下持续跟进源码演进。
- 桌面应用
【免费下载链接】alt-tab-macos
Windows alt-tab on macOS
相关推荐
Comprehensive Rust 多语言翻译工作流实战指南:基于 Gettext 的 .po 文件本地化体系
Comprehensive Rust 多语言翻译工作流实战指南:基于 Gettext 的 .po 文件本地化体系 Comprehensive Rust 是 Go
文档教程SeleniumBase 多语言测试指南:10 种语言的本地化测试编写与 translate 翻译 API 实战
SeleniumBase 多语言测试指南:10 种语言的本地化测试编写与 translate 翻译 API 实战 SeleniumBase 内置了一套"翻译测试
测试网页爬虫RPAKickstarter-iOS本地化工作流:多语言管理与翻译协作
Kickstarter iOS本地化工作流:多语言管理与翻译协作 在全球化应用开发中,本地化(Localization,L10n)是连接产品与全球用户的核心环节
移动开发
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考