ios-localization

ios-localization

热门

在 iOS/macOS 应用中实现、审查或改进本地化和国际化——字符串目录 (.xcstrings)、生成的本地化符号、稳定的键命名、LocalizedStringKey、LocalizedStringResource、复数规则、数字/日期/测量的 FormatStyle、从右到左布局、动态类型以及区域感知格式化。适用于添加多语言支持、设置字符串目录、启用编译时安全的本地化键生成符号、处理复数形式、为不同区域格式化日期/数字/货币、测试本地化或使 UI 在阿拉伯语和希伯来语等 RTL 语言中正确显示。

931Star
0Fork
更新于 2026/7/26
SKILL.md
readonly只读
name
ios-localization
description

在 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 之前,请使用此显式资源清单回答:

  1. Package.swift 声明了 defaultLocalization
  2. 目标的 resources 列表处理了目录位置,例如 .process("Resources")
  3. Localizable.xcstrings 确实位于该处理的目标资源路径内。
    仅在这些通过后,使用 bundle: .moduleText(..., 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+ 个项目

英文仅使用 oneother。阿拉伯文使用全部六种。始终提供 other 作为后备。

// 代码——单个插值触发复数支持
Text("\(unreadCount) 条未读消息")

// 字符串目录条目 (英文):
//   one:   "%lld 条未读消息"
//   other: "%lld 条未读消息"

设备变体

字符串目录支持特定设备的文本(iPhone vs iPad vs Mac):

// 在字符串目录编辑器中,为某个键启用“按设备变化”
// iPhone: "轻点继续"
// iPad:   "轻点或点击继续"
// Mac:    "点击继续"

当附近单词需要根据值的数量或性别进行屈折变化时,使用 Foundation 的自动语法一致标记。为翻译人员保留完整的屈折短语;请参阅 自动语法一致

FormatStyle——区域感知格式化

切勿硬编码面向用户的格式。使用 FormatStyle 并在对比区域(如 en_USde_DEar_SAja_JP)下测试输出。

当问题是区域感知的用户界面显示(包括数字、日期、货币、单位、名称、列表、日历、分隔符和区域预览/测试)时,ios-localization 拥有 FormatStyle 指导。对于自定义 FormatStyleParseableFormatStyle、解析、Date.IntervalFormatStyleURL.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 中的 LocalizedStringKeyString(localized:)
  • [ ] 用户可见文本没有字符串拼接
  • [ ] 日期和数字使用 FormatStyle,而不是硬编码格式
  • [ ] 通过字符串目录复数变体处理复数化(而不是手动 if/else)
  • [ ] 布局使用 .leading / .trailing,而不是 .left / .right
  • [ ] UI 已使用长文本(德文)和 RTL(阿拉伯文)测试
  • [ ] 字符串目录包含所有目标语言
  • [ ] 需要 RTL 镜像的图像使用 .flipsForRightToLeftLayoutDirection(true)
  • [ ] App Intents 和小组件使用 LocalizedStringResource
  • [ ] 新代码中没有使用 NSLocalizedString
  • [ ] 为歧义键提供了注释(为翻译人员提供上下文)
  • [ ] 使用 @ScaledMetric 处理必须随动态类型缩放的空间
  • [ ] 货币格式化使用显式货币代码,而不是区域默认值
  • [ ] 伪本地化已测试(带重音、从右到左、双倍长度)
  • [ ] 手动管理的键使用稳定的符号风格名称,而不是英文文本作为键
  • [ ] 为具有手动管理键的目标启用了“生成字符串目录符号”
  • [ ] 确保本地化字符串类型是 Sendable;使用 @MainActor 处理区域更改的 UI 更新

参考资料