axiom-localization by charleswiltgen/axiom
npx skills add https://github.com/charleswiltgen/axiom --skill axiom-localization使用字符串目录进行应用本地化的全面指南。Apple 设计奖包容性获奖者总是支持多种语言,并提供出色的 RTL(从右到左)支持。
字符串目录(.xcstrings)是 Xcode 15 用于管理应用本地化的统一格式。它们取代了传统的 .strings 和 .stringsdict 文件,采用基于 JSON 的单一格式,更易于维护、对比和集成到翻译工作流程中。
本技能涵盖字符串目录、SwiftUI/UIKit 本地化 API、复数处理、RTL 支持、区域感知格式设置以及从传统格式迁移的策略。
.strings/.stringsdict 文件迁移.xcstrings)广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
#bundle 宏和 AI 驱动的注释生成LocalizedStringResource.strings 文件方法 1:Xcode 导航器
Localizable.xcstrings)方法 2:自动提取
Xcode 15 可以自动从以下位置提取字符串:
Text、Label、Button 中的字符串字面量)String(localized:))NSLocalizedString)CFCopyLocalizedString).storyboard、.xib)所需的构建设置:
每个条目包含:
示例 .xcstrings JSON:
{
"sourceLanguage" : "en",
"strings" : {
"Thanks for shopping with us!" : {
"comment" : "Label above checkout button",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Thanks for shopping with us!"
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "¡Gracias por comprar con nosotros!"
}
}
}
}
},
"version" : "1.0"
}
Xcode 跟踪每个翻译的状态:
工作流程:
带有 String 参数的 SwiftUI 视图自动支持本地化:
// ✅ 自动可本地化
Text("Welcome to WWDC!")
Label("Thanks for shopping with us!", systemImage: "bag")
Button("Checkout") { }
// Xcode 将这些字符串提取到字符串目录
工作原理:SwiftUI 内部使用 LocalizedStringKey,它在字符串目录中查找字符串。
用于在 Swift 代码中进行显式本地化:
// 基础用法
let title = String(localized: "Welcome to WWDC!")
// 为翻译人员提供注释
let title = String(localized: "Welcome to WWDC!",
comment: "Notification banner title")
// 使用自定义表
let title = String(localized: "Welcome to WWDC!",
table: "WWDCNotifications",
comment: "Notification banner title")
// 使用默认值(键 ≠ 英文文本)
let title = String(localized: "WWDC_NOTIFICATION_TITLE",
defaultValue: "Welcome to WWDC!",
comment: "Notification banner title")
最佳实践:始终包含 comment 以为翻译人员提供上下文。
用于将可本地化字符串传递给其他函数:
import Foundation
struct CardView: View {
let title: LocalizedStringResource
let subtitle: LocalizedStringResource
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: 10.0)
VStack {
Text(title) // 在渲染时解析
Text(subtitle)
}
.padding()
}
}
}
// 用法
CardView(
title: "Recent Purchases",
subtitle: "Items you've ordered in the past week."
)
关键区别:LocalizedStringResource 延迟查找直到使用时,允许自定义视图完全可本地化。
// Markdown 格式在本地化中得以保留
let subtitle = AttributedString(localized: "**Bold** and _italic_ text")
// 基础用法
let title = NSLocalizedString("Recent Purchases", comment: "Button Title")
// 使用表
let title = NSLocalizedString("Recent Purchases",
tableName: "Shopping",
comment: "Button Title")
// 使用包
let title = NSLocalizedString("Recent Purchases",
tableName: nil,
bundle: .main,
value: "",
comment: "Button Title")
let customBundle = Bundle(for: MyFramework.self)
let text = customBundle.localizedString(forKey: "Welcome",
value: nil,
table: "MyFramework")
// Objective-C
#define MyLocalizedString(key, comment) \
[myBundle localizedStringForKey:key value:nil table:nil]
本地化应用名称、权限等:
Info.plistInfoPlist.strings:// InfoPlist.strings(西班牙语)
"CFBundleName" = "Mi Aplicación";
"NSCameraUsageDescription" = "La app necesita acceso a la cámara para tomar fotos.";
不同语言有不同的复数规则:
// Xcode 自动创建复数变体
Text("\(count) items")
// 使用自定义格式
Text("\(visitorCount) Recent Visitors")
在字符串目录中:
{
"strings" : {
"%lld Recent Visitors" : {
"localizations" : {
"en" : {
"variations" : {
"plural" : {
"one" : {
"stringUnit" : {
"state" : "translated",
"value" : "%lld Recent Visitor"
}
},
"other" : {
"stringUnit" : {
"state" : "translated",
"value" : "%lld Recent Visitors"
}
}
}
}
}
}
}
}
}
导出以供翻译时(文件 → 导出本地化):
传统(stringsdict):
<trans-unit id="/%lld Recent Visitors:dict/NSStringLocalizedFormatKey:dict/:string">
<source>%#@recentVisitors@</source>
</trans-unit>
<trans-unit id="/%lld Recent Visitors:dict/recentVisitors:dict/one:dict/:string">
<source>%lld Recent Visitor</source>
<target state="new">%lld Visitante Recente</target>
</trans-unit>
字符串目录(更简洁):
<trans-unit id="%lld Recent Visitors|==|plural.one">
<source>%lld Recent Visitor</source>
<target>%lld Visitante Recente</target>
</trans-unit>
<trans-unit id="%lld Recent Visitors|==|plural.other">
<source>%lld Recent Visitors</source>
<target>%lld Visitantes Recentes</target>
</trans-unit>
// 具有不同复数形式的多个变量
let message = String(localized: "\(songCount) songs on \(albumCount) albums")
Xcode 为每个变量的复数形式创建变体:
songCount:单数、复数albumCount:单数、复数不同平台使用不同的文本:
// 相同代码,不同设备使用不同字符串
Text("Bird Food Shop")
字符串目录变体:
{
"Bird Food Shop" : {
"localizations" : {
"en" : {
"variations" : {
"device" : {
"applewatch" : {
"stringUnit" : {
"value" : "Bird Food"
}
},
"other" : {
"stringUnit" : {
"value" : "Bird Food Shop"
}
}
}
}
}
}
}
}
结果:
用于动态类型和尺寸类别:
Text("Application Settings")
字符串目录可以为窄宽度提供更短的文本。
SwiftUI 自动为 RTL 语言镜像布局:
// ✅ 自动为阿拉伯语/希伯来语镜像
HStack {
Image(systemName: "chevron.right")
Text("Next")
}
// iPhone(英语):[>] Next
// iPhone(阿拉伯语):Next [<]
始终使用语义方向:
// ✅ 正确 - 自动镜像
.padding(.leading, 16)
.frame(maxWidth: .infinity, alignment: .leading)
// ❌ 错误 - 不镜像
.padding(.left, 16)
.frame(maxWidth: .infinity, alignment: .left)
标记应该/不应该翻转的图像:
// ✅ 方向性 - 为 RTL 镜像
Image(systemName: "chevron.forward")
// ✅ 非方向性 - 从不镜像
Image(systemName: "star.fill")
// 自定义图像
Image("backButton")
.flipsForRightToLeftLayoutDirection(true)
Xcode 方案:
模拟器:设置 → 通用 → 语言与地区 → 首选语言顺序
SwiftUI 预览:
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environment(\.layoutDirection, .rightToLeft)
.environment(\.locale, Locale(identifier: "ar"))
}
}
let formatter = DateFormatter()
formatter.locale = Locale.current // ✅ 使用当前区域设置
formatter.dateStyle = .long
formatter.timeStyle = .short
let dateString = formatter.string(from: Date())
// 美国:"January 15, 2024 at 3:30 PM"
// 法国:"15 janvier 2024 à 15:30"
// 日本:"2024年1月15日 15:30"
切勿硬编码日期格式字符串:
// ❌ 错误 - 在其他区域设置中会出错
formatter.dateFormat = "MM/dd/yyyy"
// ✅ 正确 - 适应区域设置
formatter.dateStyle = .short
let formatter = NumberFormatter()
formatter.locale = Locale.current
formatter.numberStyle = .currency
let priceString = formatter.string(from: 29.99)
// 美国:"$29.99"
// 英国:"£29.99"
// 日本:"¥30"(四舍五入为整数)
// 法国:"29,99 €"(逗号小数,符号前有空格)
let distance = Measurement(value: 100, unit: UnitLength.meters)
let formatter = MeasurementFormatter()
formatter.locale = Locale.current
let distanceString = formatter.string(from: distance)
// 美国:"328 ft"(转换为英制)
// 公制国家:"100 m"
let names = ["Ångström", "Zebra", "Apple"]
// ✅ 区域感知排序
let sorted = names.sorted { (lhs, rhs) in
lhs.localizedStandardCompare(rhs) == .orderedAscending
}
// 瑞典:["Ångström", "Apple", "Zebra"] (Å 在瑞典语中排第一)
// 美国:["Ångström", "Apple", "Zebra"] (Å 被视为 A)
import AppIntents
struct ShowTopDonutsIntent: AppIntent {
static var title: LocalizedStringResource = "Show Top Donuts"
@Parameter(title: "Timeframe")
var timeframe: Timeframe
static var parameterSummary: some ParameterSummary {
Summary("\(.applicationName) Trends for \(\.$timeframe)") {
\.$timeframe
}
}
}
字符串目录自动提取:
本地化短语:
英语:"Food Truck Trends for this week"
西班牙语:"Tendencias de Food Truck para esta semana"
struct FoodTruckShortcuts: AppShortcutsProvider {
static var appShortcuts: [AppShortcut] {
AppShortcut(
intent: ShowTopDonutsIntent(),
phrases: [
"\(.applicationName) Trends for \(\.$timeframe)",
"Show trending donuts for \(\.$timeframe) in \(.applicationName)",
"Give me trends for \(\.$timeframe) in \(.applicationName)"
]
)
}
}
Xcode 将所有 3 个短语提取到字符串目录中进行翻译。
自动迁移:
.strings 文件.xcstrings 并保留翻译手动方法:
.strings 文件复数文件自动合并:
.strings 和 .stringsdict 放在一起.xcstrings阶段 1:新代码使用字符串目录
Localizable.xcstringsString(localized:) 编写新代码.strings 文件阶段 2:迁移现有字符串
.strings 表NSLocalizedString 调用的代码阶段 3:删除传统文件
.strings 和 .stringsdict 文件共存:.strings 和 .xcstrings 可以一起工作 - Xcode 会同时检查两者。
// ❌ 错误 - 不可本地化
Text("Welcome")
let title = "Settings"
// ✅ 正确 - 可本地化
Text("Welcome") // SwiftUI 自动本地化
let title = String(localized: "Settings")
// ❌ 错误 - 词序因语言而异
let message = String(localized: "You have") + " \(count) " + String(localized: "items")
// ✅ 正确 - 带替换的单个可本地化字符串
let message = String(localized: "You have \(count) items")
为何错误:有些语言将数字放在名词前,有些放在后。
// ❌ 错误 - 对许多语言语法不正确
Text("\(count) item(s)")
// ✅ 正确 - 正确的复数处理
Text("\(count) items") // Xcode 创建复数变体
// ❌ 错误 - 在 RTL 语言中会出错
.padding(.left, 20)
HStack {
backButton
Spacer()
title
}
// ✅ 正确 - 自动镜像
.padding(.leading, 20)
HStack {
backButton // 在 RTL 中出现在右侧
Spacer()
title
}
// ❌ 错误 - 仅限美国格式
let formatter = DateFormatter()
formatter.dateFormat = "MM/dd/yyyy"
// ✅ 正确 - 适应区域设置
formatter.dateStyle = .short
formatter.locale = Locale.current
// ❌ 错误 - 翻译人员没有上下文
String(localized: "Confirm")
// ✅ 正确 - 清晰的上下文
String(localized: "Confirm", comment: "Button to confirm delete action")
影响:"Confirm" 可能意味着 "verify" 或 "acknowledge" - 上下文对于准确翻译很重要。
原因:构建设置未启用
解决方案:
原因 1:语言未添加到项目
原因 2:字符串标记为 "陈旧"
原因:使用 String.localizedStringWithFormat 而不是字符串目录
解决方案:使用字符串目录的自动复数处理:
// ✅ 正确
Text("\(count) items")
// ❌ 错误
Text(String.localizedStringWithFormat(NSLocalizedString("%d items", comment: ""), count))
原因:"Localization Prefers String Catalogs" 未设置
解决方案:
原因 1:构建设置未启用
解决方案:
原因 2:字符串未手动添加到目录
解决方案:符号仅针对手动添加的字符串生成(字符串目录中的 + 按钮)。自动提取的字符串不生成符号。
原因:语法错误或缺少导入
解决方案:
import Foundation // #bundle 必需
Text("My Collections", bundle: #bundle, comment: "Section title")
验证使用的是 #bundle 而不是 .module。
原因 1:字符串不在字符串目录中
.xcstrings 文件中原因 2:构建设置未启用
Xcode 26 引入了类型安全的本地化与生成的符号、使用设备端 AI 的自动注释生成,以及通过 #bundle 宏改进的 Swift Package 支持。基于 WWDC 2025 第 225 场会议 "Explore localization with Xcode"。
问题:基于字符串的本地化在出现拼写错误时会静默失败。
// ❌ 拼写错误 - 在运行时静默失败
Text("App.HomeScren.Title") // Screen 中缺少 'e'
解决方案:Xcode 26 从手动添加的字符串生成类型安全的符号。
// ✅ 类型安全 - 编译器捕获拼写错误
Text(.appHomeScreenTitle)
| 字符串类型 | 生成的符号类型 | 使用示例 |
|---|---|---|
| 无占位符 | 静态属性 | Text(.introductionTitle) |
| 带占位符 | 带标签参数的函数 | .subtitle(friendsPosts: 42) |
键名转换:
App.HomeScreen.Title → .appHomeScreenTitleLocalizedStringResource 上可用// SwiftUI 视图
struct ContentView: View {
var body: some View {
NavigationStack {
Text(.introductionTitle)
.navigationSubtitle(.subtitle(friendsPosts: 42))
}
}
}
// Foundation String
let message = String(localized: .curatedCollection)
// 使用 LocalizedStringResource 的自定义视图
struct CollectionDetailEditingView: View {
let title: LocalizedStringResource
init(title: LocalizedStringResource) {
self.title = title
}
var body: some View {
Text(title)
}
}
CollectionDetailEditingView(title: .editingTitle)
Xcode 26 使用设备端模型自动为可本地化字符串生成上下文注释。
对于一个按钮字符串,Xcode 生成:
"The text label on a button to cancel the deletion of a collection"
此上下文帮助翻译人员理解字符串的使用位置和方式。
自动生成的注释在导出的 XLIFF 文件中标记:
<trans-unit id="Grand Canyon" xml:space="preserve">
<source>Grand Canyon</source>
<target state="new">Grand Canyon</target>
<note from="auto-generated">Suggestion for searching landmarks</note>
</trans-unit>
好处:
SwiftUI 默认使用 .main 包。Swift Packages 和框架需要引用它们自己的包:
// ❌ 错误 - 使用主包,找不到字符串
Text("My Collections", comment: "Section title")
#bundle 宏自动引用当前目标的正确包:
// ✅ 正确 - 自动使用包/框架包
Text("My Collections", bundle: #bundle, comment: "Section title")
关键优势:
.module 包管理// 主应用
Text("My Collections",
tableName: "Discover",
comment: "Section title")
// 框架或 Swift Package
Text("My Collections",
tableName: "Discover",
bundle: #bundle,
comment: "Section title")
使用多个字符串目录进行组织时:
符号可直接在 LocalizedStringResource 上访问:
Text(.welcomeMessage) // 来自 Localizable.xcstrings
注意:Xcode 自动从默认的 "Localizable" 表解析符号。很少需要显式选择表——仅用于调试或测试特定目录。
符号嵌套在表命名空间中:
// 来自 Discover.xcstrings
Text(Discover.featuredCollection)
// 来自 Settings.xcstrings
Text(Settings.privacyPolicy)
大型应用的组织策略:
Xcode 26 支持两种互补的工作流程:
流程:
Text、Button)和 String(localized:)优点:初始设置简单,立即开始
缺点:对字符串组织的控制较少
// ✅ 字符串提取工作流程
Text("Welcome to WWDC!", comment: "Main welcome message")
流程:
优点:更好的控制、类型安全、跨框架更易于维护
缺点:需要提前规划字符串目录结构
// ✅ 生成的符号工作流程
Text(.welcomeMessage)
| 工作流程 | 最适合 | 权衡 |
|---|---|---|
| 字符串提取 | 新项目、简单应用、原型设计 | 自动提取,对组织的控制较少 |
| 生成的符号 | 大型应用、框架、多个团队 | 类型安全,更好的组织,需要提前规划 |
Xcode 26 允许在不手动重写的情况下在工作流程之间转换。
示例:
// 之前
Text("Welcome to WWDC!", comment: "Main welcome message")
// 重构后
Text(.welcomeToWWDC)
好处:
采用 Xcode 26 生成的符号后,验证:
构建配置:
字符串目录设置:
Swift Package 集成:
Text() 和 String(localized:) 调用都使用 bundle: #bundle#bundle 的地方添加了导入 Foundation重构和迁移:
可选功能:
测试:
WWDC:2025-225、2023-10155、2022-10110
文档:/xcode/localization、/xcode/localizing-and-varying-text-with-a-string-catalog
技能:axiom-app-intents-ref、axiom-hig、axiom-accessibility-diag
每周安装次数
100
仓库
GitHub 星标数
606
首次出现
2026 年 1 月 21 日
安全审计
安装于
opencode83
claude-code78
codex78
gemini-cli76
cursor74
github-copilot72
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
118,000 周安装