若要整合 Mac App 中重複的訊息,請為每個共用含義指定一個在地化索引鍵和一個 Swift 定義,並在訊息出現的所有位置使用它。請將參數、複數規則和翻譯者備註集中管理;僅英文文字相同並不足夠。
本逐步指南適用於要在 SwiftUI 和 Interface Builder 視窗中維護選取項目摘要的 Mac 開發者。實際任務是以一致方式顯示例如 2 items selected in Batch A. 的訊息,而不必在 XIB 和 Swift 程式碼中分別維護句子範本。以下所有數量和標籤皆為合成範例。
判斷哪些重複訊息應歸為一組
編輯目錄之前,請先定義訊息契約:句子的含義、接受哪些值,以及出現在哪些位置。本例的契約如下:
| 契約項目 | 定義 |
|---|---|
| 含義 | 指定集合中目前選取的項目數量 |
| 索引鍵 | selection.summary |
| 表格 | Localizable |
| Bundle | 主 App Bundle |
| 參數 1 | Int64 選取數量 |
| 參數 2 | 集合的文字標籤,例如 Batch A |
| 變化形式 | 由選取數量決定的複數形式 |
| 語境 | 在 SwiftUI 和 AppKit 標籤中顯示的完整狀態句子 |
使用此契約判斷是否應整合訊息。若兩處訊息的含義、參數和文法角色相符,就可以共用。選取項目數量和總項目數量應分開處理,即使兩者目前都顯示 2 items 也一樣。名為 Select 的按鈕也需要自己的訊息,因為它表達的是動作。
Apple 支援使用明確索引鍵搭配獨立預設值,專門用來消除文字歧義。如此一來,你可以依訊息用途命名,同時保留易讀的原始文字。註解提供語境;索引鍵則識別目錄項目。String 的明確索引鍵初始化器。
也要將表格和 Bundle 納入查詢契約。位於其他表格的索引鍵是不同的查詢目標。Apple 說明可透過 table 或 tableName 參數選取表格,並建議透過 LocalizedStringResource 傳遞可在地化的文字。本例會將輔助程式和目錄放在 App 目標中,並使用主 Bundle。準備 App 文字以供翻譯。
了解擷取與執行階段格式化的界線
擷取發生在 Xcode 建置期間:Xcode 會找出受支援的可在地化運算式,並將其原始文字與目錄同步。執行階段在地化則是在 App 解析訊息並代入實際值時發生。在目錄中找到項目,代表擷取流程找到了它;但這不代表每個控制項都能正確顯示它。
Interface Builder 有自己的資源工作流程。Apple 說明了與 storyboard 或 XIB 配對的目錄,而 Swift 原始碼通常會加入 Localizable 表格。文件所述的工作流程並未提及跨資源別名,能僅因可見文字相同就合併訊息。實用且受支援的方法是在介面載入後,從 Swift 指派共用的在地化訊息。這是根據 Apple 文件所載 API 推導出的實作方式。探索字串目錄。
SwiftUI 也區分在地化索引鍵字面值和一般字串值。例如,Text("Summary") 會要求在地化,而傳入 String 變數則會直接顯示其值,不會進行查詢。將執行階段字串包裝在 LocalizedStringKey 中會要求查詢,但不能因此認定擷取流程已找出它所有可能的值。LocalizedStringKey。
因此,以下設計會將可擷取的字面值保留在單一輔助程式中。它會將數量和標籤放入 LocalizedStringResource,然後在需要完整字串的位置解析該資源。Apple 說明此資源類型可延後進行在地化查詢。macOS 13 和後續版本皆提供此類型,也是本例的最低部署目標。LocalizedStringResource。
在 Swift 中定義一次共用訊息
建立 macOS App 專案並使用 SwiftUI,將部署目標設為 macOS 13 或後續版本,然後將 Localizable.xcstrings 加入 App 目標。根據目前的 Xcode 文件,建立方式為 File > New > File from Template;搜尋 string 並選取 String Catalog。使用字串目錄在地化並依情境變換文字。
將產生的 App 進入點替換為以下完整程式。移除原本的 App 進入點,讓目標中只保留一個 @main 宣告。SwiftUI 視窗可獨立於 XIB 執行;下一節會使用隨附的 AppKit 子類別,將現有的 Interface Builder 標籤接上共用訊息。
import AppKit
import Foundation
import SwiftUI
enum SelectionMessages {
static func summary(
count: Int64,
collectionLabel: String
) -> LocalizedStringResource {
LocalizedStringResource(
"selection.summary",
defaultValue: "\(count) items selected in \(collectionLabel).",
table: "Localizable",
comment: "Complete selection-status sentence used in SwiftUI and an AppKit label. Argument 1 is the selected-item count; argument 2 is a literal collection label. Pluralize by argument 1."
)
}
}
struct SelectionDemoView: View {
private let counts: [Int64] = [0, 1, 2]
private let collectionLabel = "Batch A"
var body: some View {
VStack(alignment: .leading, spacing: 12) {
ForEach(counts, id: \.self) { count in
Text(String(localized: SelectionMessages.summary(
count: count,
collectionLabel: collectionLabel
)))
}
}
.padding(24)
.frame(minWidth: 360, minHeight: 160)
}
}
final class SelectionSummaryField: NSTextField {
override func awakeFromNib() {
super.awakeFromNib()
// Synthetic initial state for the Interface Builder example.
updateSelection(count: 2, collectionLabel: "Batch A")
}
func updateSelection(count: Int64, collectionLabel: String) {
stringValue = String(localized: SelectionMessages.summary(
count: count,
collectionLabel: collectionLabel
))
}
}
@main
struct SelectionDemoApp: App {
var body: some Scene {
WindowGroup {
SelectionDemoView()
}
}
}
靜態索引鍵用來識別訊息;內插的預設值則提供原始句子和執行階段參數。初始化器也接受表格、Bundle 和翻譯者註解。預設 Bundle 為主 Bundle。LocalizedStringResource 的明確索引鍵初始化器。
標籤 Batch A 是插入句子中的範例內容,並非另一個訊息索引鍵。這項區別可讓集合標籤與周圍的介面文字分開。如果實際 App 使用預先定義且經過翻譯的集合名稱,請先透過各自的定義將這些名稱在地化,再於此處傳入。
不要將此句子組合成多個分別在地化的片段,例如數量、items selected in 和標籤。將完整句子納入契約,可讓在地化人員一次調整整體措辭和參數順序。
讓 Interface Builder 標籤使用相同定義
對於現有的 XIB 或 storyboard,請使用靜態 NSTextField 標籤顯示摘要。將其自訂類別設為 SelectionSummaryField,並將模組設為 App 目標。清除設計階段字串值中的舊句子,因為此標籤現在會從共用的 Swift 定義取得文字。
介面載入時,子類別會呼叫 super.awakeFromNib() 並指定合成的初始摘要。Apple 說明 awakeFromNib() 是介面載入後的呼叫點,此時輸出口和動作連線皆已建立,且必須呼叫超類別實作。awakeFromNib()。
在實際視窗中,每當選取項目變更時,請對已連線的標籤呼叫 updateSelection(count:collectionLabel:),並使用與傳給 SwiftUI 相同的模型值。將合成的初始指派替換為視窗實際的初始化流程。載入時只指派一次,無法追蹤之後的選取項目變更。
AppKit 配接器會設定 stringValue,這是 Apple 文件中指派 NSTextField 文字的方式。這樣一來,版面配置仍由 Interface Builder 管理,而訊息則由 Swift 負責。請確保寬度或換行空間足以容納完整句子。NSTextField。
將無關的靜態介面文字保留在現有資源工作流程中。這次遷移只涵蓋一則共用訊息;不必將所有標籤都移到 Swift。也請確認繫結或其他控制器指派不會在後續以舊文字覆蓋摘要。
設定複數變化形式,維持參數不變
選擇 Product > Build,開啟 Localizable.xcstrings,並找到 selection.summary。按住 Control 鍵並點按該項目,選擇 Vary by Plural;若 Xcode 詢問要依哪個參數變化,請選取數值數量參數。Apple 文件說明了這項適用於內插訊息的工作流程。使用字串目錄在地化並依情境變換文字。
對於英文來源在地化,請設定單數形式顯示 1 item selected in Batch A.,其他形式則顯示例如 2 items selected in Batch A. 的句子。保留 Xcode 提供的兩個參數佔位符。請編輯周圍的措辭,而不要用範例數量或標籤取代佔位符。
| 合成輸入 | 設定複數形式後預期的英文結果 |
|---|---|
數量 0,標籤 Batch A |
0 items selected in Batch A. |
數量 1,標籤 Batch A |
1 item selected in Batch A. |
數量 2,標籤 Batch A |
2 items selected in Batch A. |
數量 2,標籤 Batch B |
2 items selected in Batch B. |
這些是說明用途的驗收案例,並非實測結果。在設定複數形式之前,預設原始句子對所有數量都仍包含 items。擷取會保留內插訊息,但不會自行修正文法。
最後一列中變更的標籤有助於診斷問題:如果顯示內容仍是 Batch A,可能是範例值被硬編碼在某種變化形式中,或呼叫端提供了過期狀態。如果標籤消失,請先檢查該變化形式的佔位符,再考慮修改輔助程式的簽章。
移除舊項目之前,先驗證每種顯示路徑
分別測試 SwiftUI 視窗和實際的 Interface Builder 視窗。兩者使用相同的數量和標籤。檢查初次載入、後續選取項目變更,以及較長的合成標籤,例如 Batch A Extended。確認措辭一致,且句子仍清晰易讀。
針對每種支援的在地化,選擇 Product > Scheme > Edit Scheme,選取 Run,開啟 Options,並設定 App Language。再次執行 App,檢查兩種顯示路徑。Apple 說明了這些 Scheme 設定,可用來測試在地化,而無須變更整個系統的語言。執行 App 時測試在地化。
結果不正確時,請依下列順序檢查:
- 找不到項目: 確認目錄屬於 App 目標,並建置相關 Scheme。檢查是否已啟用
Use Compiler to Extract Swift Strings。 - 意外顯示原始文字: 檢查索引鍵、表格、Bundle 和翻譯;不要將可讀的備用文字視為成功。
- 只有 Interface Builder 標籤不同: 檢查其自訂類別、模組、載入路徑和後續指派。
- 只有後續值不同: 檢查兩種介面是否都收到更新後的模型值。
- 複數措辭錯誤: 檢查用來決定變化形式的數值參數,以及所有適用的變化形式。
Apple 說明了編譯器擷取設定,並指出原始碼中不再出現的已翻譯項目可能會標示為 Stale。這是清理的提示,不能證明資源已不再使用。刪除項目及其翻譯之前,請先檢查舊的介面指派和 Swift 呼叫端。探索字串目錄。
當兩個介面都從同一個定義取得相同且符合語境的訊息、參數保持完整,而且舊句子範本已無任何呼叫端時,整合工作便告完成。請將契約和驗收案例放在輔助程式旁邊,確保日後修改措辭時,兩個視窗共用的含義仍能保留。
來源
準備 App 文字以供翻譯 — Apple
使用字串目錄在地化並依情境變換文字 — Apple
探索字串目錄 — Apple
LocalizedStringKey — Apple
LocalizedStringResource — Apple
init(localized:defaultValue:table:bundle:locale:comment:) — Apple
NSTextField — Apple
awakeFromNib() — Apple
執行 App 時測試在地化 — Apple