本地化新特性
What's new in localization
2025年6月12日
一句话判断
如果你还在用 .strings 文件做本地化,今年是迁移 String Catalog 的最后窗口——增量翻译检测和内置机器翻译已经让旧方案显得原始。
这场 Session 讲了什么
String Catalog 推出两年了,今年终于像个成熟工具该有的样子。最实用的改进是增量翻译检测:你改了英文源文本,系统自动把所有目标语言的对应翻译标成”需更新”,diff 视图里直接高亮变更。这解决的是一个每天都在发生的问题——PM 改了个按钮文案,你忘了通知翻译同事,上线后发现日语还是旧的。以前这种事故要靠人肉检查,现在自动化了。
另一个大变化是 Xcode 内置了 Apple 翻译框架。在 String Catalog 里新增一条字符串,系统直接给你生成各语言的翻译建议。“确定""取消""搜索”这类 UI 常用文本的准确率已经够用,审核一下就能采纳。这把”支持一门新语言”的成本从周级别压到了小时级别。当然,品牌名、带上下文的营销文案还是得人工来,但作为初稿的起点已经很值了。
SwiftUI 这边,复数形式终于原生支持了。Text("^[\(count) 个项目](inflect: true)") 就能搞定中英文的复数差异,不用再手写 .stringsdict。本地化预览也正式进入工作流——同一个 View 开三个 #Preview,分别设中文、德语、阿拉伯语,开发时就能发现德语长文本撑破布局的问题,不用等 QA 阶段才修。
值得深挖的点
增量翻译检测:为什么这件事比听起来重要
表面上看,“源文本改了自动标脏”不是什么火箭科技。但如果你在真实项目里踩过本地化的坑,就知道这个功能打中了要害。
传统流程是这样的:开发者改了英文文案,提交代码,翻译团队收到一批 .strings 文件,然后人肉比对哪些 key 变了。问题是——key 就是源文本本身。你把 “Submit” 改成 “Send”,在 String Catalog 眼里这是两个完全不同的 key,旧的 “Submit” 的翻译不会自动关联到 “Send” 上。如果没有增量检测,这条翻译就静默丢失了。
增量检测的机制是追踪 key 的变更历史而非简单匹配 key 名。它知道 “Submit” → “Send” 是同一条字符串的修改,而不是删除旧的、新增一条。这个区别很关键:前者保留了所有已有翻译的关联,后者会让翻译归零。在快节奏的迭代中,一个按钮文案可能一周改三次,如果没有这个机制,翻译质量会随着迭代次数线性下降。
Trade-off 在于:这个功能深度绑定了 String Catalog 格式。如果你的项目用的是第三方翻译平台(如 Lokalise、Crowdin),增量检测的元数据不一定能导出。Apple 的意图很明确——把整个本地化工具链收拢到 Xcode 内部。对小团队来说这是福音,省掉了外部工具的配置成本;对大团队来说,可能需要评估和现有翻译管线的兼容性。
本地化预览:不只是”能切换语言”
本地化预览听起来像是 Preview 里加了个 locale 参数,但它的真正价值在于改变了你发现问题的时机。
过去多语言布局问题的发现路径是:开发完成 → 翻译完成 → QA 在德语/阿拉伯语环境下测试 → 提 bug → 开发修复。这个链条短则一周,长则跨迭代。一个德语按钮文案超长导致的截断问题,可能在 Sprint 3 才被发现,但按钮相关的代码已经在 Sprint 1 定型了,改起来牵一发动全身。
本地化预览把这个发现点前移到了写代码的时刻。你写一个按钮组件,顺手加三个 Preview——英文、德语、阿拉伯语。德语长了?当场改。阿拉伯语镜像有问题?当场修。问题是即时反馈的,修复成本是最低的。
这里有个隐含的最佳实践值得展开:Preview 不是用来”看效果”的,它是活的规格说明。当你的团队要求关键页面必须有三语言 Preview 时,这些 Preview 实际上成了布局的回归测试——如果有人改了按钮宽度导致德语截断,Preview 会立刻显示异常。虽然 Xcode Preview 不像 XCTest 那样能自动断言,但视觉检查在代码审查阶段就能完成。
代码片段
场景 1:复数形式一行搞定
// 购物车显示商品数量,中英文复数规则自动适配
Text("^[\(count) 个项目](inflect: true)")
// 中文: "5 个项目" / 英文: "5 items" / 英文: "1 item"
坑:inflect 依赖 String Catalog 中的翻译条目,如果目标语言没有对应翻译,fallback 到源语言时复数规则可能不对。
场景 2:多语言 Preview 配置
#Preview("德语长文本") {
CheckoutView()
.environment(\.locale, Locale(identifier: "de_DE"))
}
#Preview("阿拉伯语 RTL") {
CheckoutView()
.environment(\.locale, Locale(identifier: "ar"))
.environment(\.layoutDirection, .rightToLeft)
}
坑:RTL 预览只能检查布局镜像,无法验证文本对齐(如阿拉伯语数字仍应左对齐),需要运行时确认。
场景 3:代码中获取本地化字符串
// 非 SwiftUI 场景,如构建通知、分享文本
let greeting = String(localized: "welcome_message")
// 带参数,比 String(format:) 更安全
let msg = String(localized: "user_greeting \(userName)")
坑:String(localized:) 的 key 是源语言文本本身,如果源文本改了但代码里没同步更新,会静默 fallback 到 key 原文显示,没有任何编译警告。
最佳实践
建议分三步走。第一,这周就把项目里最活跃的模块迁移到 String Catalog——用 Xcode 的 Editor → Migrate to String Catalog,十分钟搞定,旧的 .strings 文件先保留别删,跑一周确认没问题再清理。第二,给 Checkout、Profile、Settings 这三个用户最常看到的页面加上三语言 Preview,写进团队的 PR checklist 里。第三,把自动翻译当”初稿生成器”用,常见 UI 文本直接采纳,营销相关的一律人工过。
不建议一步到位全量迁移。String Catalog 和 .strings 可以共存,先让团队习惯新工具的节奏,再逐步扩展覆盖范围。最关键的一点:迁移后一定要检查 key 名——String Catalog 用源文本做 key,如果之前 .strings 里的 key 和显示文本不一致(比如 btn_submit = "提交订单"),迁移后 key 会变成中文原文,这个变化可能影响到动态拼接 key 的代码逻辑。
还有什么值得关注
- 简繁体变体管理升级:zh-Hans、zh-Hant-TW、zh-Hant-HK 现在是独立变体,终于不用一套繁体打天下了。
.stringsdict的复数规则被inflect参数取代后,只在 NSAttributedString 等非 SwiftUI 场景还有用。- Apple Translation 框架的集成意味着未来可能支持更多语言的离线翻译,对隐私敏感的应用是个好消息。