axiom-localization
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseLocalization & Internationalization
应用本地化与国际化
Comprehensive guide to app localization using String Catalogs. Apple Design Award Inclusivity winners always support multiple languages with excellent RTL (Right-to-Left) support.
本指南全面介绍如何使用String Catalogs进行应用本地化。获得Apple设计奖包容性奖项的应用始终支持多种语言,并具备出色的RTL(从右到左)布局支持。
Overview
概述
String Catalogs () are Xcode 15's unified format for managing app localization. They replace legacy and files with a single JSON-based format that's easier to maintain, diff, and integrate with translation workflows.
.xcstrings.strings.stringsdictThis skill covers String Catalogs, SwiftUI/UIKit localization APIs, plural handling, RTL support, locale-aware formatting, and migration strategies from legacy formats.
String Catalogs()是Xcode 15推出的统一应用本地化管理格式。它以单一的JSON格式替代了传统的和文件,更易于维护、对比差异,且能更好地与翻译工作流集成。
.xcstrings.strings.stringsdict本内容涵盖String Catalogs、SwiftUI/UIKit本地化API、复数处理、RTL支持、区域感知格式设置,以及从传统格式迁移的策略。
When to Use This Skill
适用场景
- Setting up String Catalogs in Xcode 15+
- Localizing SwiftUI and UIKit apps
- Handling plural forms correctly (critical for many languages)
- Supporting RTL languages (Arabic, Hebrew)
- Formatting dates, numbers, and currencies by locale
- Migrating from legacy /
.stringsfiles.stringsdict - Preparing App Shortcuts and App Intents for localization
- Debugging missing translations or incorrect plural forms
- 在Xcode 15+中设置String Catalogs
- 本地化SwiftUI和UIKit应用
- 正确处理复数形式(对多种语言至关重要)
- 支持RTL语言(阿拉伯语、希伯来语)
- 根据区域设置日期、数字和货币格式
- 从传统的.strings/.stringsdict文件迁移
- 为App Shortcuts和App Intents准备本地化内容
- 调试缺失的翻译或错误的复数形式
System Requirements
系统要求
- Xcode 15+ for String Catalogs ()
.xcstrings - Xcode 26+ for automatic symbol generation, macro, and AI-powered comment generation
#bundle - iOS 15+ for
LocalizedStringResource - iOS 16+ for App Shortcuts localization
- Earlier iOS versions use legacy files
.strings
- Xcode 15+:支持String Catalogs()
.xcstrings - Xcode 26+:支持自动符号生成、宏和AI驱动的注释生成
#bundle - iOS 15+:支持
LocalizedStringResource - iOS 16+:支持App Shortcuts本地化
- 更早的iOS版本需使用传统的.strings文件
Part 1: String Catalogs (WWDC 2023/10155)
第一部分:String Catalogs(WWDC 2023/10155)
Creating a String Catalog
创建String Catalog
Method 1: Xcode Navigator
- File → New → File
- Choose "String Catalog"
- Name it (e.g., )
Localizable.xcstrings - Add to target
Method 2: Automatic Extraction
Xcode 15 can automatically extract strings from:
- SwiftUI views (string literals in ,
Text,Label)Button - Swift code ()
String(localized:) - Objective-C ()
NSLocalizedString - C ()
CFCopyLocalizedString - Interface Builder files (,
.storyboard).xib - Info.plist values
- App Shortcuts phrases
Build Settings Required:
- "Use Compiler to Extract Swift Strings" → Yes
- "Localization Prefers String Catalogs" → Yes
方法1:Xcode导航栏
- 文件 → 新建 → 文件
- 选择“String Catalog”
- 命名(例如)
Localizable.xcstrings - 添加到目标工程
方法2:自动提取
Xcode 15可自动从以下来源提取字符串:
- SwiftUI视图(、
Text、Label中的字符串字面量)Button - Swift代码()
String(localized:) - Objective-C()
NSLocalizedString - C语言()
CFCopyLocalizedString - Interface Builder文件(、
.storyboard).xib - Info.plist值
- App Shortcuts短语
所需构建设置:
- "Use Compiler to Extract Swift Strings" → 是
- "Localization Prefers String Catalogs" → 是
String Catalog Structure
String Catalog结构
Each entry has:
- Key: Unique identifier (default: the English string)
- Default Value: Fallback if translation missing
- Comment: Context for translators
- String Table: Organization container (default: "Localizable")
Example JSON:
.xcstringsjson
{
"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"
}每个条目包含:
- Key:唯一标识符(默认:英文字符串)
- Default Value:翻译缺失时的回退值
- Comment:给翻译人员的上下文说明
- String Table:组织容器(默认:"Localizable")
示例 JSON:
.xcstringsjson
{
"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"
}Translation States
翻译状态
Xcode tracks state for each translation:
- New (⚪) - String hasn't been translated yet
- Needs Review (🟡) - Source changed, translation may be outdated
- Reviewed (✅) - Translation approved and current
- Stale (🔴) - String no longer found in source code
Workflow:
- Developer adds string → New
- Translator adds translation → Reviewed
- Developer changes source → Needs Review
- Translator updates → Reviewed
- Developer removes code → Stale
Xcode会跟踪每个翻译的状态:
- 新建(⚪):字符串尚未翻译
- 需审核(🟡):源字符串已更改,翻译可能过时
- 已审核(✅):翻译已批准且为最新版本
- 已失效(🔴):源代码中已不再使用该字符串
工作流:
- 开发者添加字符串 → 新建
- 翻译人员添加翻译 → 已审核
- 开发者修改源字符串 → 需审核
- 翻译人员更新翻译 → 已审核
- 开发者移除代码 → 已失效
Part 2: SwiftUI Localization
第二部分:SwiftUI本地化
LocalizedStringKey (Automatic)
LocalizedStringKey(自动本地化)
SwiftUI views with parameters automatically support localization:
Stringswift
// ✅ Automatically localizable
Text("Welcome to WWDC!")
Label("Thanks for shopping with us!", systemImage: "bag")
Button("Checkout") { }
// Xcode extracts these strings to String CatalogHow it works: SwiftUI uses internally, which looks up strings in String Catalogs.
LocalizedStringKey带有参数的SwiftUI视图自动支持本地化:
Stringswift
// ✅ 自动支持本地化
Text("Welcome to WWDC!")
Label("Thanks for shopping with us!", systemImage: "bag")
Button("Checkout") { }
// Xcode会将这些字符串提取到String Catalog工作原理:SwiftUI内部使用,它会在String Catalogs中查找对应的字符串。
LocalizedStringKeyString(localized:) with Comments
带注释的String(localized:)
For explicit localization in Swift code:
swift
// Basic
let title = String(localized: "Welcome to WWDC!")
// With comment for translators
let title = String(localized: "Welcome to WWDC!",
comment: "Notification banner title")
// With custom table
let title = String(localized: "Welcome to WWDC!",
table: "WWDCNotifications",
comment: "Notification banner title")
// With default value (key ≠ English text)
let title = String(localized: "WWDC_NOTIFICATION_TITLE",
defaultValue: "Welcome to WWDC!",
comment: "Notification banner title")Best practice: Always include to give translators context.
comment在Swift代码中显式本地化:
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")最佳实践:始终添加,为翻译人员提供上下文。
commentLocalizedStringResource (Deferred Localization)
LocalizedStringResource(延迟本地化)
For passing localizable strings to other functions:
swift
import Foundation
struct CardView: View {
let title: LocalizedStringResource
let subtitle: LocalizedStringResource
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: 10.0)
VStack {
Text(title) // Resolved at render time
Text(subtitle)
}
.padding()
}
}
}
// Usage
CardView(
title: "Recent Purchases",
subtitle: "Items you've ordered in the past week."
)Key difference: defers lookup until used, allowing custom views to be fully localizable.
LocalizedStringResource用于将可本地化字符串传递给其他函数:
swift
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."
)核心区别:会延迟查找直到使用时,允许自定义视图完全支持本地化。
LocalizedStringResourceAttributedString with Markdown
带Markdown的AttributedString
swift
// Markdown formatting is preserved across localizations
let subtitle = AttributedString(localized: "**Bold** and _italic_ text")swift
// Markdown格式在本地化中会被保留
let subtitle = AttributedString(localized: "**Bold** and _italic_ text")Part 3: UIKit & Foundation
第三部分:UIKit & Foundation
NSLocalizedString Macro
NSLocalizedString宏
swift
// Basic
let title = NSLocalizedString("Recent Purchases", comment: "Button Title")
// With table
let title = NSLocalizedString("Recent Purchases",
tableName: "Shopping",
comment: "Button Title")
// With bundle
let title = NSLocalizedString("Recent Purchases",
tableName: nil,
bundle: .main,
value: "",
comment: "Button Title")swift
// 基础用法
let title = NSLocalizedString("Recent Purchases", comment: "Button Title")
// 带表名
let title = NSLocalizedString("Recent Purchases",
tableName: "Shopping",
comment: "Button Title")
// 带Bundle
let title = NSLocalizedString("Recent Purchases",
tableName: nil,
bundle: .main,
value: "",
comment: "Button Title")Bundle.localizedString
Bundle.localizedString
swift
let customBundle = Bundle(for: MyFramework.self)
let text = customBundle.localizedString(forKey: "Welcome",
value: nil,
table: "MyFramework")swift
let customBundle = Bundle(for: MyFramework.self)
let text = customBundle.localizedString(forKey: "Welcome",
value: nil,
table: "MyFramework")Custom Macros
自定义宏
objc
// Objective-C
#define MyLocalizedString(key, comment) \
[myBundle localizedStringForKey:key value:nil table:nil]objc
// Objective-C
#define MyLocalizedString(key, comment) \
[myBundle localizedStringForKey:key value:nil table:nil]Info.plist Localization
Info.plist本地化
Localize app name, permissions, etc.:
- Select
Info.plist - Editor → Add Localization
- Create for each language:
InfoPlist.strings
// InfoPlist.strings (Spanish)
"CFBundleName" = "Mi Aplicación";
"NSCameraUsageDescription" = "La app necesita acceso a la cámara para tomar fotos.";本地化应用名称、权限说明等:
- 选择
Info.plist - 编辑器 → 添加本地化
- 为每种语言创建:
InfoPlist.strings
// InfoPlist.strings (Spanish)
"CFBundleName" = "Mi Aplicación";
"NSCameraUsageDescription" = "La app necesita acceso a la cámara para tomar fotos.";Part 4: Pluralization
第四部分:复数处理
Different languages have different plural rules:
- English: 2 forms (one, other)
- Russian: 3 forms (one, few, many)
- Polish: 3 forms (one, few, other)
- Arabic: 6 forms (zero, one, two, few, many, other)
不同语言有不同的复数规则:
- 英语:2种形式(单数、其他)
- 俄语:3种形式(单数、少数、多数)
- 波兰语:3种形式(单数、少数、其他)
- 阿拉伯语:6种形式(零、单数、双数、少数、多数、其他)
SwiftUI Plural Handling
SwiftUI复数处理
swift
// Xcode automatically creates plural variations
Text("\(count) items")
// With custom formatting
Text("\(visitorCount) Recent Visitors")In String Catalog:
json
{
"strings" : {
"%lld Recent Visitors" : {
"localizations" : {
"en" : {
"variations" : {
"plural" : {
"one" : {
"stringUnit" : {
"state" : "translated",
"value" : "%lld Recent Visitor"
}
},
"other" : {
"stringUnit" : {
"state" : "translated",
"value" : "%lld Recent Visitors"
}
}
}
}
}
}
}
}
}swift
// Xcode会自动创建复数变体
Text("\(count) items")
// 自定义格式
Text("\(visitorCount) Recent Visitors")在String Catalog中:
json
{
"strings" : {
"%lld Recent Visitors" : {
"localizations" : {
"en" : {
"variations" : {
"plural" : {
"one" : {
"stringUnit" : {
"state" : "translated",
"value" : "%lld Recent Visitor"
}
},
"other" : {
"stringUnit" : {
"state" : "translated",
"value" : "%lld Recent Visitors"
}
}
}
}
}
}
}
}
}XLIFF Export Format
XLIFF导出格式
When exporting for translation (File → Export Localizations):
Legacy (stringsdict):
xml
<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>%lld Visitante Recente</target>
</trans-unit>String Catalog (cleaner):
xml
<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>导出用于翻译时(文件 → 导出本地化):
传统格式(stringsdict):
xml
<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>%lld Visitante Recente</target>
</trans-unit>String Catalog格式(更简洁):
xml
<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>Substitutions with Plural Variables
带复数变量的替换
swift
// Multiple variables with different plural forms
let message = String(localized: "\(songCount) songs on \(albumCount) albums")Xcode creates variations for each variable's plural form:
- : one, other
songCount - : one, other
albumCount - Total combinations: 2 × 2 = 4 translation entries
swift
// 多个变量,各有不同复数形式
let message = String(localized: "\(songCount) songs on \(albumCount) albums")Xcode会为每个变量的复数形式创建变体:
- :单数、其他
songCount - :单数、其他
albumCount - 总组合数:2 × 2 = 4个翻译条目
Part 5: Device & Width Variations
第五部分:设备与宽度变体
Device-Specific Strings
设备特定字符串
Different text for different platforms:
swift
// Same code, different strings per device
Text("Bird Food Shop")String Catalog variations:
json
{
"Bird Food Shop" : {
"localizations" : {
"en" : {
"variations" : {
"device" : {
"applewatch" : {
"stringUnit" : {
"value" : "Bird Food"
}
},
"other" : {
"stringUnit" : {
"value" : "Bird Food Shop"
}
}
}
}
}
}
}
}Result:
- iPhone/iPad: "Bird Food Shop"
- Apple Watch: "Bird Food" (shorter for small screen)
不同平台使用不同文本:
swift
// 同一代码,不同设备显示不同字符串
Text("Bird Food Shop")String Catalog中的变体:
json
{
"Bird Food Shop" : {
"localizations" : {
"en" : {
"variations" : {
"device" : {
"applewatch" : {
"stringUnit" : {
"value" : "Bird Food"
}
},
"other" : {
"stringUnit" : {
"value" : "Bird Food Shop"
}
}
}
}
}
}
}
}效果:
- iPhone/iPad:"Bird Food Shop"
- Apple Watch:"Bird Food"(适配小屏幕的短文本)
Width Variations
宽度变体
For dynamic type and size classes:
swift
Text("Application Settings")String Catalog can provide shorter text for narrow widths.
适配动态类型和尺寸类:
swift
Text("Application Settings")String Catalog可为窄屏提供更短的文本。
Part 6: RTL Support
第六部分:RTL支持
Layout Mirroring
布局镜像
SwiftUI automatically mirrors layouts for RTL languages:
swift
// ✅ Automatically mirrors for Arabic/Hebrew
HStack {
Image(systemName: "chevron.right")
Text("Next")
}
// iPhone (English): [>] Next
// iPhone (Arabic): Next [<]SwiftUI会自动为RTL语言镜像布局:
swift
// ✅ 自动为阿拉伯语/希伯来语镜像布局
HStack {
Image(systemName: "chevron.right")
Text("Next")
}
// iPhone(英语): [>] Next
// iPhone(阿拉伯语): Next [<]Leading/Trailing vs Left/Right
前导/尾随 vs 左/右
Always use semantic directions:
swift
// ✅ Correct - mirrors automatically
.padding(.leading, 16)
.frame(maxWidth: .infinity, alignment: .leading)
// ❌ Wrong - doesn't mirror
.padding(.left, 16)
.frame(maxWidth: .infinity, alignment: .left)始终使用语义化方向:
swift
// ✅ 正确 - 自动镜像
.padding(.leading, 16)
.frame(maxWidth: .infinity, alignment: .leading)
// ❌ 错误 - 不会镜像
.padding(.left, 16)
.frame(maxWidth: .infinity, alignment: .left)Images and Icons
图片与图标
Mark images that should/shouldn't flip:
swift
// ✅ Directional - mirrors for RTL
Image(systemName: "chevron.forward")
// ✅ Non-directional - never mirrors
Image(systemName: "star.fill")
// Custom images
Image("backButton")
.flipsForRightToLeftLayoutDirection(true)标记需要/不需要翻转的图片:
swift
// ✅ 方向敏感 - RTL时镜像
Image(systemName: "chevron.forward")
// ✅ 非方向敏感 - 从不镜像
Image(systemName: "star.fill")
// 自定义图片
Image("backButton")
.flipsForRightToLeftLayoutDirection(true)Testing in RTL Mode
测试RTL模式
Xcode Scheme:
- Edit Scheme → Run → Options
- Application Language: Arabic / Hebrew
- OR: App Language → Right-to-Left Pseudolanguage
Simulator:
Settings → General → Language & Region → Preferred Language Order
SwiftUI Preview:
swift
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environment(\.layoutDirection, .rightToLeft)
.environment(\.locale, Locale(identifier: "ar"))
}
}Xcode Scheme:
- 编辑Scheme → 运行 → 选项
- 应用语言:阿拉伯语/希伯来语
- 或:应用语言 → 从右到左伪语言
模拟器:
设置 → 通用 → 语言与地区 → 首选语言顺序
SwiftUI预览:
swift
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environment(\.layoutDirection, .rightToLeft)
.environment(\.locale, Locale(identifier: "ar"))
}
}Part 7: Locale-Aware Formatting
第七部分:区域感知格式设置
DateFormatter
DateFormatter
swift
let formatter = DateFormatter()
formatter.locale = Locale.current // ✅ Use current locale
formatter.dateStyle = .long
formatter.timeStyle = .short
let dateString = formatter.string(from: Date())
// US: "January 15, 2024 at 3:30 PM"
// France: "15 janvier 2024 à 15:30"
// Japan: "2024年1月15日 15:30"Never hardcode date format strings:
swift
// ❌ Wrong - breaks in other locales
formatter.dateFormat = "MM/dd/yyyy"
// ✅ Correct - adapts to locale
formatter.dateStyle = .shortswift
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"切勿硬编码日期格式字符串:
swift
// ❌ 错误 - 在其他区域会失效
formatter.dateFormat = "MM/dd/yyyy"
// ✅ 正确 - 适配区域
formatter.dateStyle = .shortNumberFormatter for Currency
NumberFormatter(货币)
swift
let formatter = NumberFormatter()
formatter.locale = Locale.current
formatter.numberStyle = .currency
let priceString = formatter.string(from: 29.99)
// US: "$29.99"
// UK: "£29.99"
// Japan: "¥30" (rounds to integer)
// France: "29,99 €" (comma decimal, space before symbol)swift
let formatter = NumberFormatter()
formatter.locale = Locale.current
formatter.numberStyle = .currency
let priceString = formatter.string(from: 29.99)
// 美国: "$29.99"
// 英国: "£29.99"
// 日本: "¥30"(取整为整数)
// 法国: "29,99 €"(逗号作为小数点,符号前加空格)MeasurementFormatter
MeasurementFormatter
swift
let distance = Measurement(value: 100, unit: UnitLength.meters)
let formatter = MeasurementFormatter()
formatter.locale = Locale.current
let distanceString = formatter.string(from: distance)
// US: "328 ft" (converts to imperial)
// Metric countries: "100 m"swift
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"Locale-Specific Sorting
区域特定排序
swift
let names = ["Ångström", "Zebra", "Apple"]
// ✅ Locale-aware sort
let sorted = names.sorted { (lhs, rhs) in
lhs.localizedStandardCompare(rhs) == .orderedAscending
}
// Sweden: ["Ångström", "Apple", "Zebra"] (Å comes first in Swedish)
// US: ["Ångström", "Apple", "Zebra"] (Å treated as A)swift
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)Part 8: App Shortcuts Localization
第八部分:App Shortcuts本地化
Phrases with Parameters
带参数的短语
swift
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
}
}
}String Catalog automatically extracts:
- Intent title
- Parameter names
- Phrase templates with placeholders
Localized phrases:
English: "Food Truck Trends for this week"
Spanish: "Tendencias de Food Truck para esta semana"swift
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
}
}
}String Catalog自动提取:
- Intent标题
- 参数名称
- 带占位符的短语模板
本地化短语:
English: "Food Truck Trends for this week"
Spanish: "Tendencias de Food Truck para esta semana"AppShortcutsProvider Localization
AppShortcutsProvider本地化
swift
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 extracts all 3 phrases into String Catalog for translation.
swift
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个短语提取到String Catalog中进行翻译。
Part 9: Migration from Legacy
第九部分:从传统格式迁移
Converting .strings to .xcstrings
将.strings转换为.xcstrings
Automatic migration:
- Select file in Navigator
.strings - Editor → Convert to String Catalog
- Xcode creates and preserves translations
.xcstrings
Manual approach:
- Create new String Catalog
- Build project (Xcode extracts strings from code)
- Import translations via File → Import Localizations (XLIFF)
- Delete old files
.strings
自动迁移:
- 在导航栏中选择.strings文件
- 编辑器 → 转换为String Catalog
- Xcode创建.xcstrings并保留现有翻译
手动方法:
- 创建新的String Catalog
- 构建项目(Xcode从代码中提取字符串)
- 通过文件 → 导入本地化(XLIFF)导入翻译
- 删除旧的.strings文件
Converting .stringsdict
将.stringsdict转换为.xcstrings
Plural files automatically merge:
- Keep and
.stringstogether.stringsdict - Convert → Both merge into single
.xcstrings - Plural variations preserved
复数文件自动合并:
- 保留.strings和.stringsdict文件在一起
- 转换 → 两者合并为单个.xcstrings文件
- 复数变体将被保留
Gradual Migration Strategy
逐步迁移策略
Phase 1: New code uses String Catalogs
- Create
Localizable.xcstrings - Write new code with
String(localized:) - Keep legacy files for old code
.strings
Phase 2: Migrate existing strings
- Convert one table at a time
.strings - Test translations after each conversion
- Update code using old calls
NSLocalizedString
Phase 3: Remove legacy files
- Delete and
.stringsfiles.stringsdict - Verify all strings in String Catalog
- Submit to App Store
Coexistence: and work together - Xcode checks both.
.strings.xcstrings阶段1:新代码使用String Catalogs
- 创建
Localizable.xcstrings - 新代码使用
String(localized:) - 保留旧代码使用的传统.strings文件
阶段2:迁移现有字符串
- 一次转换一个.strings表
- 每次转换后测试翻译
- 更新使用旧调用的代码
NSLocalizedString
阶段3:移除传统文件
- 删除.strings和.stringsdict文件
- 验证所有字符串都在String Catalog中
- 提交到App Store
共存:.strings和.xcstrings可同时工作——Xcode会检查两者。
Common Mistakes
常见错误
Hardcoded Strings
硬编码字符串
swift
// ❌ Wrong - not localizable
Text("Welcome")
let title = "Settings"
// ✅ Correct - localizable
Text("Welcome") // SwiftUI auto-localizes
let title = String(localized: "Settings")swift
// ❌ 错误 - 不支持本地化
Text("Welcome")
let title = "Settings"
// ✅ 正确 - 支持本地化
Text("Welcome") // SwiftUI自动本地化
let title = String(localized: "Settings")Concatenating Localized Strings
拼接本地化字符串
swift
// ❌ Wrong - word order varies by language
let message = String(localized: "You have") + " \(count) " + String(localized: "items")
// ✅ Correct - single localizable string with substitution
let message = String(localized: "You have \(count) items")Why wrong: Some languages put numbers before nouns, some after.
swift
// ❌ 错误 - 词序因语言而异
let message = String(localized: "You have") + " \(count) " + String(localized: "items")
// ✅ 正确 - 单个带替换的可本地化字符串
let message = String(localized: "You have \(count) items")错误原因:有些语言把数字放在名词前,有些放在后。
Missing Plural Forms
缺失复数形式
swift
// ❌ Wrong - grammatically incorrect for many languages
Text("\(count) item(s)")
// ✅ Correct - proper plural handling
Text("\(count) items") // Xcode creates plural variationsswift
// ❌ 错误 - 对多种语言语法不正确
Text("\(count) item(s)")
// ✅ 正确 - 正确处理复数
Text("\(count) items") // Xcode创建复数变体Ignoring RTL
忽略RTL
swift
// ❌ Wrong - breaks in RTL languages
.padding(.left, 20)
HStack {
backButton
Spacer()
title
}
// ✅ Correct - mirrors automatically
.padding(.leading, 20)
HStack {
backButton // Appears on right in RTL
Spacer()
title
}swift
// ❌ 错误 - 在RTL语言中会失效
.padding(.left, 20)
HStack {
backButton
Spacer()
title
}
// ✅ 正确 - 自动镜像
.padding(.leading, 20)
HStack {
backButton // 在RTL中显示在右侧
Spacer()
title
}Wrong Date/Number Formats
错误的日期/数字格式
swift
// ❌ Wrong - US-only format
let formatter = DateFormatter()
formatter.dateFormat = "MM/dd/yyyy"
// ✅ Correct - adapts to locale
formatter.dateStyle = .short
formatter.locale = Locale.currentswift
// ❌ 错误 - 仅适用于美国区域
let formatter = DateFormatter()
formatter.dateFormat = "MM/dd/yyyy"
// ✅ 正确 - 适配区域
formatter.dateStyle = .short
formatter.locale = Locale.currentForgetting Comments
缺失注释
swift
// ❌ Wrong - translator has no context
String(localized: "Confirm")
// ✅ Correct - clear context
String(localized: "Confirm", comment: "Button to confirm delete action")Impact: "Confirm" could mean "verify" or "acknowledge" - context matters for accurate translation.
swift
// ❌ 错误 - 翻译人员无上下文
String(localized: "Confirm")
// ✅ 正确 - 清晰的上下文
String(localized: "Confirm", comment: "Button to confirm delete action")影响:"Confirm"可能表示"验证"或"确认"——上下文对准确翻译至关重要。
Troubleshooting
故障排除
Strings not appearing in String Catalog
字符串未出现在String Catalog中
Cause: Build settings not enabled
Solution:
- Build Settings → "Use Compiler to Extract Swift Strings" → Yes
- Clean Build Folder (Cmd+Shift+K)
- Build project
原因:构建设置未启用
解决方案:
- 构建设置 → "Use Compiler to Extract Swift Strings" → 是
- 清理构建文件夹(Cmd+Shift+K)
- 构建项目
Translations not showing in app
翻译未在应用中显示
Cause 1: Language not added to project
- Project → Info → Localizations → + button
- Add target language
Cause 2: String marked as "Stale"
- Remove stale strings or verify code still uses them
原因1:项目未添加目标语言
- 项目 → 信息 → 本地化 → +按钮
- 添加目标语言
原因2:字符串标记为"已失效"
- 移除失效字符串或验证代码仍在使用它们
Plural forms incorrect
复数形式不正确
Cause: Using instead of String Catalog
String.localizedStringWithFormatSolution: Use String Catalog's automatic plural handling:
swift
// ✅ Correct
Text("\(count) items")
// ❌ Wrong
Text(String.localizedStringWithFormat(NSLocalizedString("%d items", comment: ""), count))原因:使用而非String Catalog
String.localizedStringWithFormat解决方案:使用String Catalog的自动复数处理:
swift
// ✅ 正确
Text("\(count) items")
// ❌ 错误
Text(String.localizedStringWithFormat(NSLocalizedString("%d items", comment: ""), count))XLIFF export missing strings
XLIFF导出缺失字符串
Cause: "Localization Prefers String Catalogs" not set
Solution:
- Build Settings → "Localization Prefers String Catalogs" → Yes
- Export Localizations again
原因:"Localization Prefers String Catalogs"未设置
解决方案:
- 构建设置 → "Localization Prefers String Catalogs" → 是
- 重新导出本地化
Generated symbols not appearing (Xcode 26+)
生成的符号未显示(Xcode 26+)
Cause 1: Build setting not enabled
Solution:
- Build Settings → "Generate String Catalog Symbols" → Yes
- Clean Build Folder (Cmd+Shift+K)
- Rebuild project
Cause 2: String not manually added to catalog
Solution: Symbols only generate for manually-added strings (+ button in String Catalog). Auto-extracted strings don't generate symbols.
原因1:构建设置未启用
解决方案:
- 构建设置 → "Generate String Catalog Symbols" → 是
- 清理构建文件夹(Cmd+Shift+K)
- 重新构建项目
原因2:字符串未手动添加到Catalog
解决方案:仅手动添加到Catalog的字符串(+按钮)会生成符号。自动提取的字符串不会生成符号。
#bundle macro not working (Xcode 26+)
#bundle宏不工作(Xcode 26+)
Cause: Wrong syntax or missing import
Solution:
swift
import Foundation // Required for #bundle
Text("My Collections", bundle: #bundle, comment: "Section title")Verify you're using not .
#bundle.module原因:语法错误或缺失导入
解决方案:
swift
import Foundation // 使用#bundle需要导入
Text("My Collections", bundle: #bundle, comment: "Section title")确认使用的是而非。
#bundle.moduleRefactoring to symbols fails (Xcode 26+)
重构为符号失败(Xcode 26+)
Cause 1: String not in String Catalog
- Ensure string exists in file
.xcstrings - Build project to refresh catalog
- Try refactoring again
Cause 2: Build setting not enabled
- Enable "Generate String Catalog Symbols" in Build Settings
- Clean and rebuild
原因1:字符串不在String Catalog中
- 确保字符串存在于.xcstrings文件中
- 构建项目以刷新Catalog
- 再次尝试重构
原因2:构建设置未启用
- 在构建设置中启用"Generate String Catalog Symbols"
- 清理并重新构建
Part 10: Xcode 26 Localization Enhancements
第十部分:Xcode 26本地化增强
Xcode 26 introduces type-safe localization with generated symbols, automatic comment generation using on-device AI, and improved Swift Package support with the macro. Based on WWDC 2025 session 225 "Explore localization with Xcode".
#bundleXcode 26引入了类型安全本地化(带生成符号)、使用设备端AI自动生成注释,以及改进的Swift Package支持(通过宏)。基于WWDC 2025会议225 "Explore localization with Xcode"。
#bundleGenerated Symbols (Type-Safe Localization)
生成符号(类型安全本地化)
The problem: String-based localization fails silently when typos occur.
swift
// ❌ Typo - fails silently at runtime
Text("App.HomeScren.Title") // Missing 'e' in ScreenThe solution: Xcode 26 generates type-safe symbols from manually-added strings.
问题:基于字符串的本地化在出现拼写错误时会静默失败。
swift
// ❌ 拼写错误 - 运行时静默失败
Text("App.HomeScren.Title") // Screen缺失'e'解决方案:Xcode 26从手动添加的字符串生成类型安全符号。
How It Works
工作原理
- Add strings manually to String Catalog using the + button
- Enable build setting: "Generate String Catalog Symbols" (ON by default in new projects)
- Use symbols instead of strings
swift
// ✅ Type-safe - compiler catches typos
Text(.appHomeScreenTitle)- 手动添加字符串到String Catalog(使用+按钮)
- 启用构建设置:"Generate String Catalog Symbols"(新项目默认开启)
- 使用符号替代字符串
swift
// ✅ 类型安全 - 编译器捕获拼写错误
Text(.appHomeScreenTitle)Symbol Generation Rules
符号生成规则
| String Type | Generated Symbol Type | Usage Example |
|---|---|---|
| No placeholders | Static property | |
| With placeholders | Function with labeled arguments | |
Key naming conversion:
- →
App.HomeScreen.Title.appHomeScreenTitle - Periods removed, camel-cased
- Available on
LocalizedStringResource
| 字符串类型 | 生成的符号类型 | 使用示例 |
|---|---|---|
| 无占位符 | 静态属性 | |
| 带占位符 | 带标签参数的函数 | |
命名转换规则:
- →
App.HomeScreen.Title.appHomeScreenTitle - 移除句点,转换为驼峰式
- 可通过访问
LocalizedStringResource
Code Examples
代码示例
swift
// SwiftUI views
struct ContentView: View {
var body: some View {
NavigationStack {
Text(.introductionTitle)
.navigationSubtitle(.subtitle(friendsPosts: 42))
}
}
}
// Foundation String
let message = String(localized: .curatedCollection)
// Custom views with LocalizedStringResource
struct CollectionDetailEditingView: View {
let title: LocalizedStringResource
init(title: LocalizedStringResource) {
self.title = title
}
var body: some View {
Text(title)
}
}
CollectionDetailEditingView(title: .editingTitle)swift
// 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)Automatic Comment Generation
自动生成注释
Xcode 26 uses an on-device model to automatically generate contextual comments for localizable strings.
Xcode 26使用设备端模型自动为可本地化字符串生成上下文注释。
Enabling the Feature
启用功能
- Open Xcode Settings → Editing
- Enable "automatically generate string catalog comments"
- New strings added to code automatically receive generated comments
- 打开Xcode设置 → 编辑
- 启用"automatically generate string catalog comments"
- 添加到代码中的新字符串会自动获得生成的注释
Example
示例
For a button string, Xcode generates:
"The text label on a button to cancel the deletion of a collection"
This context helps translators understand where and how the string is used.
对于按钮字符串,Xcode生成:
"The text label on a button to cancel the deletion of a collection"
该上下文帮助翻译人员理解字符串的使用场景和方式。
XLIFF Export
XLIFF导出
Auto-generated comments are marked in exported XLIFF files:
xml
<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>Benefits:
- Saves developer time writing translator context
- Provides consistent, clear descriptions
- Improves translation quality
自动生成的注释会在导出的XLIFF文件中标记:
xml
<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>优势:
- 节省开发者编写翻译上下文的时间
- 提供一致、清晰的描述
- 提升翻译质量
Swift Package & Framework Localization
Swift Package & Framework本地化
The Problem
问题
SwiftUI uses the bundle by default. Swift Packages and frameworks need to reference their own bundle:
.mainswift
// ❌ Wrong - uses main bundle, strings not found
Text("My Collections", comment: "Section title")SwiftUI默认使用 bundle。Swift Packages和frameworks需要引用自己的bundle:
.mainswift
// ❌ 错误 - 使用主bundle,无法找到字符串
Text("My Collections", comment: "Section title")The Solution: #bundle Macro (NEW in Xcode 26)
解决方案:#bundle宏(Xcode 26新增)
The macro automatically references the correct bundle for the current target:
#bundleswift
// ✅ Correct - automatically uses package/framework bundle
Text("My Collections", bundle: #bundle, comment: "Section title")Key advantages:
- Works in main app, frameworks, and Swift Packages
- Backwards-compatible with older OS versions
- Eliminates manual bundle management
.module
#bundleswift
// ✅ 正确 - 自动使用package/framework bundle
Text("My Collections", bundle: #bundle, comment: "Section title")核心优势:
- 适用于主应用、frameworks和Swift Packages
- 向后兼容旧版OS
- 无需手动管理bundle
.module
With Custom Table Names
自定义表名
swift
// Main app
Text("My Collections",
tableName: "Discover",
comment: "Section title")
// Framework or Swift Package
Text("My Collections",
tableName: "Discover",
bundle: #bundle,
comment: "Section title")swift
// 主应用
Text("My Collections",
tableName: "Discover",
comment: "Section title")
// Framework或Swift Package
Text("My Collections",
tableName: "Discover",
bundle: #bundle,
comment: "Section title")Custom Table Symbol Access
自定义表符号访问
When using multiple String Catalogs for organization:
当使用多个String Catalogs进行组织时:
Default "Localizable" Table
默认"Localizable"表
Symbols are directly accessible on :
LocalizedStringResourceswift
Text(.welcomeMessage) // From Localizable.xcstringsNote: Xcode automatically resolves symbols from the default "Localizable" table. Explicit table selection is rarely needed—use it only for debugging or testing specific catalogs.
符号可直接通过访问:
LocalizedStringResourceswift
Text(.welcomeMessage) // 来自Localizable.xcstrings注意:Xcode自动解析默认"Localizable"表中的符号。仅在调试或测试特定Catalog时才需要显式选择表。
Custom Tables
自定义表
Symbols are nested in the table namespace:
swift
// From Discover.xcstrings
Text(Discover.featuredCollection)
// From Settings.xcstrings
Text(Settings.privacyPolicy)Organization strategy for large apps:
- Localizable.xcstrings - Core app strings
- FeatureName.xcstrings - Feature-specific strings (e.g., Onboarding, Settings, Discover)
- Benefits: Easier to manage, clearer ownership, better XLIFF organization
符号嵌套在表命名空间中:
swift
// 来自Discover.xcstrings
Text(Discover.featuredCollection)
// 来自Settings.xcstrings
Text(Settings.privacyPolicy)大型应用组织策略:
- Localizable.xcstrings - 核心应用字符串
- FeatureName.xcstrings - 功能特定字符串(例如Onboarding、Settings、Discover)
- 优势:更易于管理、所有权更清晰、XLIFF组织更合理
Two Localization Workflows
两种本地化工作流
Xcode 26 supports two complementary workflows:
Xcode 26支持两种互补的工作流:
Workflow 1: String Extraction (Recommended for new projects)
工作流1:字符串提取(推荐用于新项目)
Process:
- Write strings directly in code
- Use SwiftUI views (,
Text) andButtonString(localized:) - Xcode automatically extracts to String Catalog
- Leverage automatic comment generation
Pros: Simple initial setup, immediate start
Cons: Less control over string organization
swift
// ✅ String extraction workflow
Text("Welcome to WWDC!", comment: "Main welcome message")流程:
- 直接在代码中编写字符串
- 使用SwiftUI视图(、
Text)和ButtonString(localized:) - Xcode自动提取到String Catalog
- 利用自动注释生成
优点:初始设置简单,可快速开始
缺点:对字符串组织的控制较少
swift
// ✅ 字符串提取工作流
Text("Welcome to WWDC!", comment: "Main welcome message")Workflow 2: Generated Symbols (Recommended as complexity grows)
工作流2:生成符号(推荐用于复杂度提升时)
Process:
- Manually add strings to String Catalog
- Reference via type-safe symbols
- Organize into custom tables
Pros: Better control, type safety, easier to maintain across frameworks
Cons: Requires planning string catalog structure upfront
swift
// ✅ Generated symbols workflow
Text(.welcomeMessage)| Workflow | Best For | Trade-offs |
|---|---|---|
| String Extraction | New projects, simple apps, prototyping | Automatic extraction, less control over organization |
| Generated Symbols | Large apps, frameworks, multiple teams | Type safety, better organization, requires upfront planning |
流程:
- 手动将字符串添加到String Catalog
- 通过类型安全符号引用
- 组织到自定义表中
优点:控制更好、类型安全、跨frameworks更易维护
缺点:需要预先规划String Catalog结构
swift
// ✅ 生成符号工作流
Text(.welcomeMessage)| 工作流 | 最佳适用场景 | 权衡 |
|---|---|---|
| 字符串提取 | 新项目、简单应用、原型开发 | 自动提取,对组织的控制较少 |
| 生成符号 | 大型应用、frameworks、多团队协作 | 类型安全、组织性更好,需要预先规划 |
Refactoring Between Workflows
工作流之间的重构
Xcode 26 allows converting between workflows without manual rewriting.
Xcode 26允许在工作流之间转换,无需手动重写代码。
Converting Strings to Symbols
将字符串转换为符号
- Right-click on a string literal in code
- Select "Refactor > Convert Strings to Symbols"
- Preview all affected locations
- Customize symbol names before confirming
- Apply to entire table or individual strings
Example:
swift
// Before
Text("Welcome to WWDC!", comment: "Main welcome message")
// After refactoring
Text(.welcomeToWWDC)Benefits:
- Batch conversion of entire String Catalogs
- Preview changes before applying
- Maintain localization without code rewrites
- 右键点击代码中的字符串字面量
- 选择**"Refactor > Convert Strings to Symbols"**
- 预览所有受影响的位置
- 自定义符号名称后确认
- 应用到整个表或单个字符串
示例:
swift
// 转换前
Text("Welcome to WWDC!", comment: "Main welcome message")
// 转换后
Text(.welcomeToWWDC)优势:
- 批量转换整个String Catalogs
- 应用前预览更改
- 无需重写代码即可维护本地化
Implementation Checklist
实施检查清单
After adopting Xcode 26 generated symbols, verify:
Build Configuration:
- "Generate String Catalog Symbols" build setting enabled
- Project builds without "Cannot find 'symbolName' in scope" errors
- Clean build succeeds (Cmd+Shift+K, then Cmd+B)
String Catalog Setup:
- Strings manually added to catalog using + button (not auto-extracted)
- Symbol names follow conventions (camelCase, no periods)
- Custom tables organized by feature (if using multiple catalogs)
Swift Package Integration:
- All and
Text()calls in packages useString(localized:)bundle: #bundle - Import Foundation added where is used
#bundle - Tested package builds independently and as dependency
Refactoring & Migration:
- Tested refactoring tool on sample strings
- Preview showed expected changes before applying
- Old string-based calls still work during transition period
Optional Features:
- Automatic comment generation enabled in Xcode Settings → Editing (optional)
- Tested AI-generated comments for accuracy
- XLIFF export includes auto-generated comments
Testing:
- Symbols resolve correctly in SwiftUI previews
- Localization works across all supported languages
- App runs on minimum supported iOS version
采用Xcode 26生成符号后,验证以下内容:
构建配置:
- "Generate String Catalog Symbols"构建设置已启用
- 项目构建无"Cannot find 'symbolName' in scope"错误
- 清理构建成功(Cmd+Shift+K,然后Cmd+B)
String Catalog设置:
- 字符串通过+按钮手动添加到Catalog(非自动提取)
- 符号名称遵循约定(驼峰式,无句点)
- 自定义表按功能组织(如果使用多个Catalogs)
Swift Package集成:
- Packages中的所有和
Text()调用都使用String(localized:)bundle: #bundle - 使用的位置已导入Foundation
#bundle - 已独立测试Package构建,且作为依赖时也能正常工作
重构与迁移:
- 已在示例字符串上测试重构工具
- 应用前预览显示预期更改
- 旧的基于字符串的调用在过渡期间仍能正常工作
可选功能:
- Xcode设置 → 编辑中已启用自动注释生成(可选)
- 已测试AI生成注释的准确性
- XLIFF导出包含自动生成的注释
测试:
- 符号在SwiftUI预览中正确解析
- 本地化在所有支持的语言中正常工作
- 应用在最低支持的iOS版本上可运行
Resources
资源
WWDC: 2025-225, 2023-10155, 2022-10110
Docs: /xcode/localization, /xcode/localizing-and-varying-text-with-a-string-catalog
Skills: axiom-app-intents-ref, axiom-hig, axiom-accessibility-diag
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