ios-localization

ios-localization

熱門

在 iOS/macOS App 中實作、審查或改善在地化與國際化 — String Catalogs (.xcstrings)、產生的可在地化符號、穩定的鍵值命名、LocalizedStringKey、LocalizedStringResource、複數規則、用於數字/日期/測量的 FormatStyle、從右到左佈局、Dynamic Type 以及地區感知格式化。用於新增多語言支援、設定 String Catalogs、啟用產生的符號以獲得編譯時安全的在地化鍵值、處理複數形式、格式化不同地區的日期/數字/貨幣、測試在地化,或讓 UI 在阿拉伯語和希伯來語等 RTL 語言中正確運作。

931星標
0分支
更新於 2026/7/26
SKILL.md
唯讀
名稱
ios-localization
描述

在 iOS/macOS App 中實作、審查或改善在地化與國際化 — String Catalogs (.xcstrings)、產生的可在地化符號、穩定的鍵值命名、LocalizedStringKey、LocalizedStringResource、複數規則、用於數字/日期/測量的 FormatStyle、從右到左佈局、Dynamic Type 以及地區感知格式化。用於新增多語言支援、設定 String Catalogs、啟用產生的符號以獲得編譯時安全的在地化鍵值、處理複數形式、格式化不同地區的日期/數字/貨幣、測試在地化,或讓 UI 在阿拉伯語和希伯來語等 RTL 語言中正確運作。

iOS 在地化與國際化

使用 String Catalogs、現代字串類型、地區感知格式化和從右到左佈局,為 Apple 平台 App 進行在地化。

目錄

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 之前,先使用這個明確的資源檢查清單回答:

  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、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+ 個項目

英文只使用 oneother。阿拉伯文使用全部六個。始終提供 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_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)
// 美國: "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 中的 LocalizedStringKeyString(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 更新

參考資料