mcpField
I
ios-localization

ios-localization

github.com/dpearson2699开发1.2k4.3k 次安装13 周前更新
代码仓库

关于 ios-localization

面向 Apple 平台应用开发者的本地化与国际化指南,介绍 String Catalog、现代字符串类型、区域格式化和 RTL 布局,并提供测试与审查要点。

主要功能

  1. 01String Catalog 与生成符号
  2. 02本地化字符串类型选择
  3. 03插值与复数规则
  4. 04区域感知格式化
  5. 05RTL 布局适配
  6. 06本地化测试清单

适用场景

  1. 01将新应用字符串迁移到 String Catalog
  2. 02为视图、服务和系统界面选择字符串类型
  3. 03按地区显示日期、货币和度量单位
  4. 04检查长文本与 RTL 界面的布局问题

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/dpearson2699/swift-ios-skills --skill ios-localization

安装后在 Claude Code 中输入/ios-localization确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

evals/
evals.json
references/
formatstyle-locale.md
string-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、小组件、延迟显示的系统 UILocalizedStringResource在显示之前保留本地化信息
非面向用户的日志和分析数据普通 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 之前,请先提供以下明确的资源检查清单:

  1. Package.swift 声明了 defaultLocalization。
  2. 目标的 resources 列表处理了目录所在位置,例如 .process("Resources")。
  3. 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 项
one1 项1 项
two(不使用)2 项(双数)
few(不使用)3–10 项
many(不使用)11–99 项
other2 项以上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

参考资料

评论

支持 Markdown