关于 ios-localization
面向 Apple 平台应用开发者的本地化与国际化指南,介绍 String Catalog、现代字符串类型、区域格式化和 RTL 布局,并提供测试与审查要点。
主要功能
- 01String Catalog 与生成符号
- 02本地化字符串类型选择
- 03插值与复数规则
- 04区域感知格式化
- 05RTL 布局适配
- 06本地化测试清单
适用场景
- 01将新应用字符串迁移到 String Catalog
- 02为视图、服务和系统界面选择字符串类型
- 03按地区显示日期、货币和度量单位
- 04检查长文本与 RTL 界面的布局问题
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ dpearson2699/ swift-ios-skills --skill ios-localization安装后在 Claude Code 中输入/ios-localization确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
evals/evals.jsonreferences/formatstyle-locale.mdstring-catalogs.md详细介绍
iOS 本地化与国际化
使用字符串目录、现代字符串类型、区域设置感知格式化和从右到左布局,为 Apple 平台应用进行本地化。
目录
字符串目录与生成的符号
字符串目录是 Xcode 15+ 新本地化工作的推荐工作流。它们会将可本地化字符串、复数规则和设备变体集中在一个由 Xcode 管理且带有可视化编辑器的 JSON 文件中。迁移期间,可以同时使用旧版 .strings 和 .stringsdict 文件,但新的 Swift 和 SwiftUI 代码应默认使用字符串目录。
自动提取的工作原理:
Xcode 会在每次构建时扫描以下模式:
// SwiftUI -- automatically extracted (LocalizedStringKey)
Text("Welcome back") // key: "Welcome back"
Label("Settings", systemImage: "gear")
Button("Save") { }
Toggle("Dark Mode", isOn: $dark)
// Programmatic -- automatically extracted
String(localized: "No items found")
LocalizedStringResource("Order placed")
// Plain String: not extracted or localized
let msg = "Hello"
Xcode 会自动将发现的键添加到字符串目录中。在编辑器中将翻译标记为“需要审核”、“已翻译”或“已过期”。
有关字符串目录工作流、迁移和测试策略的详细信息,请参阅 references/string-catalogs.md。
生成的符号是 Xcode 26 在字符串目录之上的类型化访问层; 它不会改变字符串目录自 Xcode 15 起即可使用这一事实。
启用: Build Settings > Localization > Generate String Catalog Symbols → Yes(新的 Xcode 26 项目中默认启用)。需要目录格式版本 1.1。
工作流: 在字符串目录编辑器中通过 (+) 按钮手动添加键——手动键默认启用 Generate Swift Symbol 复选框。自动提取的键也可以通过 Refactor > Convert Strings to Symbols 选择启用。生成符号的字符串应使用稳定的手动键。对于面向 API 的字符串,请避免使用从源文本生成的键,因为措辞修改可能会重命名生成的标识符,导致调用位置频繁变动。
// Generated from key "room_available" in Localizable.xcstrings
Text(.roomAvailable)
// Parameterized key "landmarks_count" with %1$(count)lld
Text(.landmarksCount(count: 42))
// Non-default table "Booking.xcstrings"
Text(.Booking.confirmBookingCta)
Xcode 会将键转换为驼峰命名来生成符号名称:settings.notifications.toggle → .settingsNotificationsToggle。你可以通过 Refactor > Convert Strings to Symbols 将现有的已提取字符串转换为符号(可逆)。
生成的符号具有 internal 访问级别。若要跨模块访问,请创建一个公开的包装扩展。对于更复杂的多模块配置,请改用 xcstrings-tool。
有关生成符号的完整参考资料——包括提取状态、符号派生规则和跨模块模式——请参阅 references/string-catalogs.md。
字符串类型——选择指南
| 上下文 | 类型 | 原因 |
|---|---|---|
| SwiftUI 视图文本 | LocalizedStringKey(隐式) | SwiftUI 会执行查找 |
| 视图模型、服务和错误 | String(localized:) | 立即解析为 String |
| App Intents、小组件、延迟显示的系统 UI | LocalizedStringResource | 在显示之前保留本地化信息 |
| 非面向用户的日志和分析数据 | 普通 String | 无需本地化 |
LocalizedStringKey(SwiftUI 默认类型)
SwiftUI 视图的文本参数接受 LocalizedStringKey。字符串字面量会隐式转换——无需额外操作。
Text("Welcome back")
Button("Delete") { deleteItem() }
直接向 SwiftUI 视图初始化器传递字符串时,请使用 LocalizedStringKey。大多数情况下,不要手动构造 LocalizedStringKey。
String(localized:) —— 现代 NSLocalizedString 替代方案
在 SwiftUI 视图初始化器之外使用本地化字符串时,使用此方法。它会返回普通 String。字面量/插值初始化器适用于 iOS 15+;解析 LocalizedStringResource 适用于 iOS 16+。
let title = String(localized: "Welcome back")
let msg = String(localized: "error.network",
defaultValue: "Check your internet connection")
如果 Swift Package 本地化失败,在调试 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 = "Order Coffee"
}
func showAlert(title: LocalizedStringResource, message: LocalizedStringResource) {
let resolved = String(localized: title)
}
本地化字符串中的字符串插值
本地化字符串中的插值值会成为位置参数,供译者调整顺序。
// English: "Welcome, Alice! You have 3 new messages."
// German: "Willkommen, Alice! Sie haben 3 neue Nachrichten."
// Japanese: "Alice さん、新しいメッセージが 3 件あります。"
let text = String(localized: "Welcome, \(name)! You have \(count) new messages.")
在 String Catalog 中,这会显示为 %@ 和 %lld 占位符,译者可以调整它们的顺序:
- 英语:
"Welcome, %@! You have %lld new messages." - 日语:
"%@さん、新しいメッセージが%lld件あります。"
类型安全的插值(优先于格式说明符):
// Interpolation provides type safety
String(localized: "Score: \(score, format: .number)")
String(localized: "Due: \(date, format: .dateTime.month().day())")
复数形式
String Catalog 原生支持复数形式,无需 .stringsdict XML。
在 String Catalog 中设置
本地化字符串包含整数插值时,Xcode 会检测到它,并在 String Catalog 编辑器中提供复数变体。请为每个 CLDR 复数类别提供译文:
| 类别 | 英语示例 | 阿拉伯语示例 |
|---|---|---|
| zero | (不使用) | 0 项 |
| one | 1 项 | 1 项 |
| two | (不使用) | 2 项(双数) |
| few | (不使用) | 3–10 项 |
| many | (不使用) | 11–99 项 |
| other | 2 项以上 | 100 项以上 |
英语仅使用 one 和 other。阿拉伯语使用全部六种。请始终提供 other 作为回退形式。
// Code -- single interpolation triggers plural support
Text("\(unreadCount) unread messages")
// String Catalog entries (English):
// one: "%lld unread message"
// other: "%lld unread messages"
设备差异
String Catalog 支持针对不同设备的文本(iPhone、iPad 与 Mac):
// In String Catalog editor, enable "Vary by Device" for a key
// iPhone: "Tap to continue"
// iPad: "Tap or click to continue"
// Mac: "Click to continue"
当附近的词语必须根据某个值的数量或性别进行词形变化时,请使用 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
// Preset styles
now.formatted(date: .long, time: .shortened)
// US: "January 15, 2026 at 3:30 PM"
// DE: "15. Januar 2026 um 15:30"
// JP: "2026年1月15日 15:30"
// Component-based
now.formatted(.dateTime.month(.wide).day().year())
// US: "January 15, 2026"
// In 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) // For 0.85 -> "85%" (US) / "85 %" (FR)
// Currency
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" (auto-converts!) / DE: "5 Kilometer"
let temp = Measurement(value: 22, unit: UnitTemperature.celsius)
temp.formatted(.measurement(width: .abbreviated))
// US: "72 F" (auto-converts!) / FR: "22 C"
加载 references/formatstyle-locale.md,了解时长、姓名、列表、自定义样式、变体矩阵以及更深入的 RTL 测试。
从右向左(RTL)布局
SwiftUI 会自动镜像 RTL 语言(阿拉伯语、希伯来语、乌尔都语、波斯语)的布局。大多数视图无需任何改动。
SwiftUI 会自动镜像的内容
HStack子视图会反转顺序.leading/.trailing对齐方式和内边距会互换两侧NavigationStack返回按钮会移至尾侧List展开指示器会翻转- 文本对齐方式会遵循阅读方向
需要手动处理的内容
// Testing RTL in previews
MyView()
.environment(\.layoutDirection, .rightToLeft)
.environment(\.locale, Locale(identifier: "ar"))
// Images that should mirror (directional arrows, progress indicators)
Image(systemName: "chevron.right")
.flipsForRightToLeftLayoutDirection(true)
// Images that should NOT mirror: logos, photos, clocks, music notes
// Forced LTR for specific content (phone numbers, code)
Text("+1 (555) 123-4567")
.environment(\.layoutDirection, .leftToRight)
布局规则
- 应该使用
.leading/.trailing——它们会针对 RTL 自动翻转 - 不要使用
.left/.right——它们是固定的,会导致 RTL 布局出错 - 应该使用
HStack/VStack——它们会遵循布局方向 - 不要使用绝对定位
offset(x:)来进行方向性定位
常见错误
不要:使用固定宽度布局
// WRONG -- German text is ~30% longer than English
Text(title).frame(width: 120)
应该:使用灵活布局
// CORRECT
Text(title).fixedSize(horizontal: false, vertical: true)
// Or use VStack/wrapping that accommodates expansion
不要:跳过伪本地化测试
只用英语测试会掩盖文本截断、布局和 RTL 问题。
应该:至少使用德语(长文本)和阿拉伯语(RTL)测试
使用 Xcode 方案设置覆盖应用语言,无需更改设备区域设置。
审查清单
- 所有面向用户的字符串都使用本地化(SwiftUI 中使用
LocalizedStringKey或String(localized:)) - 面向用户的文本没有使用字符串拼接
- 日期和数字使用
FormatStyle,而非硬编码格式 - 通过 String Catalog 复数变体处理复数形式(而非手动使用 if/else)
- 布局使用
.leading/.trailing,而非.left/.right - 使用长文本(德语)和 RTL(阿拉伯语)测试 UI
- String Catalog 包含所有目标语言
- 需要 RTL 镜像的图像使用
.flipsForRightToLeftLayoutDirection(true) - App Intents 和小组件使用
LocalizedStringResource - 新代码中没有使用
NSLocalizedString - 为含义模糊的键提供注释(为译者提供上下文)
- 对必须随动态字体大小缩放的间距使用
@ScaledMetric - 货币格式使用明确的货币代码,而非区域设置默认值
- 已测试伪本地化(带重音、从右向左、双倍长度)
- 手动管理的键使用稳定的符号风格名称,而非将英语文本用作键
- 为包含手动管理键的目标启用 Generate String Catalog Symbols
- 确保本地化字符串类型遵循 Sendable;使用 @MainActor 更新因区域设置变更而变化的 UI
参考资料
- FormatStyle 模式:references/formatstyle-locale.md
- String Catalog 指南:references/string-catalogs.md