
ios-localization
熱門在 iOS/macOS App 中實作、審查或改善在地化與國際化 — String Catalogs (.xcstrings)、產生的可在地化符號、穩定的鍵值命名、LocalizedStringKey、LocalizedStringResource、複數規則、用於數字/日期/測量的 FormatStyle、從右到左佈局、Dynamic Type 以及地區感知格式化。用於新增多語言支援、設定 String Catalogs、啟用產生的符號以獲得編譯時安全的在地化鍵值、處理複數形式、格式化不同地區的日期/數字/貨幣、測試在地化,或讓 UI 在阿拉伯語和希伯來語等 RTL 語言中正確運作。
在 iOS/macOS App 中實作、審查或改善在地化與國際化 — String Catalogs (.xcstrings)、產生的可在地化符號、穩定的鍵值命名、LocalizedStringKey、LocalizedStringResource、複數規則、用於數字/日期/測量的 FormatStyle、從右到左佈局、Dynamic Type 以及地區感知格式化。用於新增多語言支援、設定 String Catalogs、啟用產生的符號以獲得編譯時安全的在地化鍵值、處理複數形式、格式化不同地區的日期/數字/貨幣、測試在地化,或讓 UI 在阿拉伯語和希伯來語等 RTL 語言中正確運作。
iOS 在地化與國際化
使用 String Catalogs、現代字串類型、地區感知格式化和從右到左佈局,為 Apple 平台 App 進行在地化。
目錄
- String Catalogs 與產生的符號
- 字串類型 — 決策指南
- 在地化字串中的字串插值
- 複數化
- FormatStyle — 地區感知格式化
- 從右到左 (RTL) 佈局
- 常見錯誤
- 在地化審查檢查清單
- 參考資料
String Catalogs 與產生的符號
String Catalogs 是 Xcode 15+ 推薦用於新在地化工作的工作流程。它們將可在地化字串、複數規則和裝置變體集中在一個 Xcode 管理的 JSON 檔案中,並提供視覺化編輯器。在遷移期間,舊的 .strings 和 .stringsdict 檔案可以共存,但新的 Swift 和 SwiftUI 程式碼應預設使用 String Catalogs。
自動提取的運作方式:
Xcode 在每次建置時掃描以下模式:
// SwiftUI — 自動提取 (LocalizedStringKey)
Text("歡迎回來") // 鍵值: "歡迎回來"
Label("設定", systemImage: "gear")
Button("儲存") { }
Toggle("深色模式", isOn: $dark)
// 程式化 — 自動提取
String(localized: "找不到項目")
LocalizedStringResource("訂單已成立")
// 純字串:不會被提取或在地化
let msg = "哈囉"
Xcode 會自動將發現的鍵值加入 String Catalog。在編輯器中將翻譯標記為「需要審查」、「已翻譯」或「過時」。
有關詳細的 String Catalog 工作流程、遷移和測試策略,請參閱 references/string-catalogs.md。
產生的符號是 Xcode 26 在 String Catalogs 之上提供的型別化存取層;它們不會改變目錄的 Xcode 15 可用性。
啟用: 建置設定 > Localization > Generate String Catalog Symbols → Yes(在 Xcode 26 新專案中預設啟用)。需要目錄格式版本 1.1。
工作流程: 透過 String Catalog 編輯器中的 (+) 按鈕手動新增鍵值 — 手動鍵值預設會啟用「產生 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、Widget、延遲系統 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 之前,先使用這個明確的資源檢查清單回答:
Package.swift宣告了defaultLocalization。- 目標的
resources列表處理了目錄位置,例如.process("Resources")。 Localizable.xcstrings確實位於該處理過的目標資源路徑內。
只有在這些都通過後,才使用bundle: .module或Text(..., bundle: .module)除錯查詢。
現有的 NSLocalizedString 字面值鍵值仍然可以透過 Xcode 工具匯出或遷移,但新的 Swift 程式碼應優先使用 String(localized:)、SwiftUI 字面值、LocalizedStringResource 或產生的符號。
LocalizedStringResource — 傳遞在地化資訊而不解析
當字串需要作為可在地化值攜帶以供後續解析時使用,特別是用於 App Intents、Widget、通知、產生的可在地化符號以及直接接受 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) 則新訊息。")
在 String Catalog 中,這會顯示為 %@ 和 %lld 佔位符,翻譯人員可以重新排序:
- 英文:
"Welcome, %@! You have %lld new messages." - 日文:
"%@さん、新しいメッセージが%lld件あります。"
型別安全的插值(優先於格式指定符):
// 插值提供型別安全
String(localized: "分數:\(score, format: .number)")
String(localized: "截止日:\(date, format: .dateTime.month().day())")
複數化
String Catalogs 原生支援複數化 — 無需 .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 作為備用。
// 程式碼 — 單一插值觸發複數支援
Text("\(unreadCount) 則未讀訊息")
// String Catalog 條目 (英文):
// one: "%lld 則未讀訊息"
// other: "%lld 則未讀訊息"
裝置變體
String Catalogs 支援裝置特定的文字(iPhone vs iPad vs Mac):
// 在 String Catalog 編輯器中,為某個鍵值啟用「依裝置變化」
// iPhone: "點擊以繼續"
// iPad: "點擊或按一下以繼續"
// Mac: "按一下以繼續"
當附近的單字需要根據數值或性別進行變化時,使用 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
// 預設樣式
now.formatted(date: .long, time: .shortened)
// 美國: "2026年1月15日 下午3:30"
// 德國: "15. Januar 2026 um 15:30"
// 日本: "2026年1月15日 15:30"
// 基於元件
now.formatted(.dateTime.month(.wide).day().year())
// 美國: "2026年1月15日"
// 在 SwiftUI 中
Text(now, format: .dateTime.month().day().year())
數字
let count = 1234567
count.formatted() // "1,234,567" (美國) / "1.234.567" (德國)
count.formatted(.number.precision(.fractionLength(2)))
count.formatted(.percent) // 0.85 -> "85%" (美國) / "85 %" (法國)
// 貨幣
let price = Decimal(29.99)
price.formatted(.currency(code: "USD")) // "$29.99" (美國) / "29,99 $US" (法國)
price.formatted(.currency(code: "EUR")) // "29,99 EUR" (德國)
測量
let distance = Measurement(value: 5, unit: UnitLength.kilometers)
distance.formatted(.measurement(width: .wide))
// 美國: "3.1 英里" (自動轉換!) / 德國: "5 公里"
let temp = Measurement(value: 22, unit: UnitTemperature.celsius)
temp.formatted(.measurement(width: .abbreviated))
// 美國: "72°F" (自動轉換!) / 法國: "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 scheme 設定來覆蓋 App 語言,無需更改裝置地區。
審查檢查清單
- [ ] 所有使用者面向的字串都使用在地化(SwiftUI 中的
LocalizedStringKey或String(localized:)) - [ ] 使用者可見文字沒有字串串接
- [ ] 日期和數字使用
FormatStyle,而非硬編碼格式 - [ ] 透過 String Catalog 複數變體處理複數化(非手動 if/else)
- [ ] 佈局使用
.leading/.trailing,而非.left/.right - [ ] UI 已使用長文字(德文)和 RTL(阿拉伯文)測試
- [ ] String Catalog 包含所有目標語言
- [ ] 需要 RTL 鏡像的圖片使用
.flipsForRightToLeftLayoutDirection(true) - [ ] App Intents 和 Widget 使用
LocalizedStringResource - [ ] 新程式碼中沒有使用
NSLocalizedString - [ ] 為不明確的鍵值提供註解(給翻譯人員的上下文)
- [ ] 使用
@ScaledMetric處理必須隨 Dynamic Type 縮放的間距 - [ ] 貨幣格式化使用明確的貨幣代碼,而非地區預設值
- [ ] 已測試偽在地化(重音、從右到左、雙倍長度)
- [ ] 手動管理的鍵值使用穩定的符號風格名稱,而非英文文字作為鍵值
- [ ] 為具有手動管理鍵值的目標啟用「產生 String Catalog 符號」
- [ ] 確保在地化字串型別是 Sendable;使用 @MainActor 處理地區變更的 UI 更新
參考資料
- FormatStyle 模式: references/formatstyle-locale.md
- String Catalogs 指南: references/string-catalogs.md



