
ios-localization
热门在 iOS/macOS 应用中实现、审查或改进本地化和国际化——字符串目录 (.xcstrings)、生成的本地化符号、稳定的键命名、LocalizedStringKey、LocalizedStringResource、复数规则、数字/日期/测量的 FormatStyle、从右到左布局、动态类型以及区域感知格式化。适用于添加多语言支持、设置字符串目录、启用编译时安全的本地化键生成符号、处理复数形式、为不同区域格式化日期/数字/货币、测试本地化或使 UI 在阿拉伯语和希伯来语等 RTL 语言中正确显示。
在 iOS/macOS 应用中实现、审查或改进本地化和国际化——字符串目录 (.xcstrings)、生成的本地化符号、稳定的键命名、LocalizedStringKey、LocalizedStringResource、复数规则、数字/日期/测量的 FormatStyle、从右到左布局、动态类型以及区域感知格式化。适用于添加多语言支持、设置字符串目录、启用编译时安全的本地化键生成符号、处理复数形式、为不同区域格式化日期/数字/货币、测试本地化或使 UI 在阿拉伯语和希伯来语等 RTL 语言中正确显示。
iOS 本地化与国际化
使用字符串目录、现代字符串类型、区域感知格式化和从右到左布局来本地化 Apple 平台应用。
目录
字符串目录与生成符号
字符串目录是 Xcode 15+ 推荐的新本地化工作流程。它将可本地化字符串、复数规则和设备变体保存在一个 Xcode 管理的 JSON 文件中,并提供可视化编辑器。旧版 .strings 和 .stringsdict 文件可以在迁移期间共存,但新的 Swift 和 SwiftUI 代码应默认使用字符串目录。
自动提取的工作原理:
Xcode 在每次构建时扫描以下模式:
// SwiftUI——自动提取 (LocalizedStringKey)
Text("欢迎回来") // 键: "欢迎回来"
Label("设置", systemImage: "gear")
Button("保存") { }
Toggle("深色模式", isOn: $dark)
// 编程方式——自动提取
String(localized: "未找到项目")
LocalizedStringResource("订单已下达")
// 纯字符串:不提取也不本地化
let msg = "你好"
Xcode 自动将发现的键添加到字符串目录中。在编辑器中标记翻译为“需要审查”、“已翻译”或“过时”。
有关详细的字符串目录工作流程、迁移和测试策略,请参阅 references/string-catalogs.md。
生成符号是 Xcode 26 在字符串目录之上的类型化访问层;它们不会改变目录的 Xcode 15 可用性。
启用: 构建设置 > 本地化 > 生成字符串目录符号 → Yes(在 Xcode 26 新项目中默认开启)。需要目录格式版本 1.1。
工作流程: 通过字符串目录编辑器中的 (+) 按钮手动添加键——手动键默认勾选“生成 Swift 符号”。自动提取的键也可以通过“重构 > 将字符串转换为符号”选择加入。对于生成符号的字符串,请使用稳定的手动键。避免使用源副本派生的键作为面向 API 的字符串,因为措辞修改可能会重命名生成的标识符并导致调用点变更。
// 从 Localizable.xcstrings 中的键 "room_available" 生成
Text(.roomAvailable)
// 参数化键 "landmarks_count",包含 %1$(count)lld
Text(.landmarksCount(count: 42))
// 非默认表 "Booking.xcstrings"
Text(.Booking.confirmBookingCta)
Xcode 通过驼峰式命名从键派生符号名称:settings.notifications.toggle → .settingsNotificationsToggle。您可以通过“重构 > 将字符串转换为符号”将现有提取的字符串转换为符号(可逆)。
生成的符号是 internal 的。对于跨模块访问,请创建一个公共包装扩展。对于更重的多模块设置,请改用 xcstrings-tool。
有关完整的生成符号参考——提取状态、符号派生规则和跨模块模式——请参阅 references/string-catalogs.md。
字符串类型——决策指南
| 上下文 | 类型 | 原因 |
|---|---|---|
| SwiftUI 视图文本 | LocalizedStringKey(隐式) |
SwiftUI 执行查找 |
| 视图模型、服务和错误 | String(localized:) |
立即解析为 String |
| App Intents、小组件、延迟系统 UI | LocalizedStringResource |
携带本地化信息直到显示 |
| 非面向用户的日志和分析 | 纯 String |
无需本地化 |
LocalizedStringKey(SwiftUI 默认)
SwiftUI 视图接受 LocalizedStringKey 作为其文本参数。字符串字面量会隐式转换——无需额外工作。
Text("欢迎回来")
Button("删除") { deleteItem() }
当直接将字符串传递给 SwiftUI 视图初始化器时,使用 LocalizedStringKey。在大多数情况下,不要手动构造 LocalizedStringKey。
String(localized:)——现代 NSLocalizedString 替代品
用于 SwiftUI 视图初始化器之外的任何本地化字符串。返回纯 String。字面量/插值初始化器在 iOS 15+ 可用;解析 LocalizedStringResource 在 iOS 16+ 可用。
let title = String(localized: "欢迎回来")
let msg = String(localized: "error.network",
defaultValue: "请检查您的互联网连接")
对于 Swift 包本地化失败,在调试 bundle 之前,请使用此显式资源清单回答:
Package.swift声明了defaultLocalization。- 目标的
resources列表处理了目录位置,例如.process("Resources")。 Localizable.xcstrings确实位于该处理的目标资源路径内。
仅在这些通过后,使用bundle: .module或Text(..., bundle: .module)调试查找。
现有的 NSLocalizedString 字面量键仍然可以通过 Xcode 工具导出或迁移,但新的 Swift 代码应优先使用 String(localized:)、SwiftUI 字面量、LocalizedStringResource 或生成符号。
LocalizedStringResource——传递本地化信息而不解析
当字符串必须作为可本地化值携带以供后续解析时使用,特别是对于 App Intents、小组件、通知、生成的本地化符号以及直接接受 LocalizedStringResource 的系统 API。当代码需要立即解析的字符串时,使用 String(localized:)。在 iOS 16+ 可用。
struct OrderCoffeeIntent: AppIntent {
static var title: LocalizedStringResource = "点咖啡"
}
func showAlert(title: LocalizedStringResource, message: LocalizedStringResource) {
let resolved = String(localized: title)
}
本地化字符串中的字符串插值
本地化字符串中的插值值成为位置参数,翻译人员可以重新排序。
// 英文: "Welcome, Alice! You have 3 new messages."
// 德文: "Willkommen, Alice! Sie haben 3 neue Nachrichten."
// 日文: "Alice さん、新しいメッセージが 3 件あります。"
let text = String(localized: "欢迎,\(name)!您有 \(count) 条新消息。")
在字符串目录中,这显示为 %@ 和 %lld 占位符,翻译人员可以重新排序:
- 英文:
"Welcome, %@! You have %lld new messages." - 日文:
"%@さん、新しいメッセージが%lld件あります。"
类型安全插值(优先于格式说明符):
// 插值提供类型安全
String(localized: "分数:\(score, format: .number)")
String(localized: "截止日期:\(date, format: .dateTime.month().day())")
复数化
字符串目录原生支持复数化——无需 .stringsdict XML。
在字符串目录中设置
当本地化字符串包含整数插值时,Xcode 会检测到并在字符串目录编辑器中提供复数变体。为每个 CLDR 复数类别提供翻译:
| 类别 | 英文示例 | 阿拉伯文示例 |
|---|---|---|
| zero | (不使用) | 0 个项目 |
| one | 1 个项目 | 1 个项目 |
| two | (不使用) | 2 个项目 (双数) |
| few | (不使用) | 3-10 个项目 |
| many | (不使用) | 11-99 个项目 |
| other | 2+ 个项目 | 100+ 个项目 |
英文仅使用 one 和 other。阿拉伯文使用全部六种。始终提供 other 作为后备。
// 代码——单个插值触发复数支持
Text("\(unreadCount) 条未读消息")
// 字符串目录条目 (英文):
// one: "%lld 条未读消息"
// other: "%lld 条未读消息"
设备变体
字符串目录支持特定设备的文本(iPhone vs iPad vs Mac):
// 在字符串目录编辑器中,为某个键启用“按设备变化”
// iPhone: "轻点继续"
// iPad: "轻点或点击继续"
// Mac: "点击继续"
当附近单词需要根据值的数量或性别进行屈折变化时,使用 Foundation 的自动语法一致标记。为翻译人员保留完整的屈折短语;请参阅 自动语法一致。
FormatStyle——区域感知格式化
切勿硬编码面向用户的格式。使用 FormatStyle 并在对比区域(如 en_US、de_DE、ar_SA 和 ja_JP)下测试输出。
当问题是区域感知的用户界面显示(包括数字、日期、货币、单位、名称、列表、日历、分隔符和区域预览/测试)时,ios-localization 拥有 FormatStyle 指导。对于自定义 FormatStyle、ParseableFormatStyle、解析、Date.IntervalFormatStyle、URL.FormatStyle 或可重用格式化器 API 设计,请路由到 swift-formatstyle;除非明确要求实现,否则将 ios-localization 的建议限制在区域风险和测试方面。
日期
let now = Date.now
// 预设样式
now.formatted(date: .long, time: .shortened)
// US: "2026年1月15日 下午3:30"
// DE: "15. Januar 2026 um 15:30"
// JP: "2026年1月15日 15:30"
// 基于组件
now.formatted(.dateTime.month(.wide).day().year())
// US: "2026年1月15日"
// 在 SwiftUI 中
Text(now, format: .dateTime.month().day().year())
数字
let count = 1234567
count.formatted() // "1,234,567" (US) / "1.234.567" (DE)
count.formatted(.number.precision(.fractionLength(2)))
count.formatted(.percent) // 对于 0.85 -> "85%" (US) / "85 %" (FR)
// 货币
let price = Decimal(29.99)
price.formatted(.currency(code: "USD")) // "$29.99" (US) / "29,99 $US" (FR)
price.formatted(.currency(code: "EUR")) // "29,99 EUR" (DE)
测量
let distance = Measurement(value: 5, unit: UnitLength.kilometers)
distance.formatted(.measurement(width: .wide))
// US: "3.1 miles" (自动转换!) / DE: "5 Kilometer"
let temp = Measurement(value: 22, unit: UnitTemperature.celsius)
temp.formatted(.measurement(width: .abbreviated))
// US: "72 F" (自动转换!) / FR: "22 C"
加载 references/formatstyle-locale.md 以获取持续时间、名称、列表、自定义样式、变体矩阵和更深入的 RTL 测试。
从右到左 (RTL) 布局
SwiftUI 会自动镜像 RTL 语言(阿拉伯语、希伯来语、乌尔都语、波斯语)的布局。大多数视图无需任何更改。
SwiftUI 自动镜像的内容
HStack子视图顺序反转.leading/.trailing对齐和填充交换边NavigationStack返回按钮移动到尾部边缘List展开指示器翻转- 文本对齐遵循阅读方向
需要手动注意的内容
// 在预览中测试 RTL
MyView()
.environment(\.layoutDirection, .rightToLeft)
.environment(\.locale, Locale(identifier: "ar"))
// 应镜像的图像(方向箭头、进度指示器)
Image(systemName: "chevron.right")
.flipsForRightToLeftLayoutDirection(true)
// 不应镜像的图像:标志、照片、时钟、音符
// 特定内容的强制 LTR(电话号码、代码)
Text("+1 (555) 123-4567")
.environment(\.layoutDirection, .leftToRight)
布局规则
- 要 使用
.leading/.trailing——它们会自动为 RTL 翻转 - 不要 使用
.left/.right——它们是固定的,会破坏 RTL - 要 使用
HStack/VStack——它们尊重布局方向 - 不要 使用绝对
offset(x:)进行方向定位
常见错误
不要:使用固定宽度布局
// 错误——德文文本比英文长约 30%
Text(title).frame(width: 120)
要:使用灵活布局
// 正确
Text(title).fixedSize(horizontal: false, vertical: true)
// 或使用能够适应扩展的 VStack/换行
不要:跳过伪本地化测试
仅测试英文会隐藏截断、布局和 RTL 错误。
要:至少使用德文(长文本)和阿拉伯文(RTL)进行测试
使用 Xcode 方案设置覆盖应用语言,而无需更改设备区域。
审查清单
- [ ] 所有面向用户的字符串都使用本地化(SwiftUI 中的
LocalizedStringKey或String(localized:)) - [ ] 用户可见文本没有字符串拼接
- [ ] 日期和数字使用
FormatStyle,而不是硬编码格式 - [ ] 通过字符串目录复数变体处理复数化(而不是手动 if/else)
- [ ] 布局使用
.leading/.trailing,而不是.left/.right - [ ] UI 已使用长文本(德文)和 RTL(阿拉伯文)测试
- [ ] 字符串目录包含所有目标语言
- [ ] 需要 RTL 镜像的图像使用
.flipsForRightToLeftLayoutDirection(true) - [ ] App Intents 和小组件使用
LocalizedStringResource - [ ] 新代码中没有使用
NSLocalizedString - [ ] 为歧义键提供了注释(为翻译人员提供上下文)
- [ ] 使用
@ScaledMetric处理必须随动态类型缩放的空间 - [ ] 货币格式化使用显式货币代码,而不是区域默认值
- [ ] 伪本地化已测试(带重音、从右到左、双倍长度)
- [ ] 手动管理的键使用稳定的符号风格名称,而不是英文文本作为键
- [ ] 为具有手动管理键的目标启用了“生成字符串目录符号”
- [ ] 确保本地化字符串类型是 Sendable;使用 @MainActor 处理区域更改的 UI 更新
参考资料
- FormatStyle 模式: references/formatstyle-locale.md
- 字符串目录指南: references/string-catalogs.md





