若要整合 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 時測試在地化。

結果不正確時,請依下列順序檢查:

  1. 找不到項目: 確認目錄屬於 App 目標,並建置相關 Scheme。檢查是否已啟用 Use Compiler to Extract Swift Strings。
  2. 意外顯示原始文字: 檢查索引鍵、表格、Bundle 和翻譯;不要將可讀的備用文字視為成功。
  3. 只有 Interface Builder 標籤不同: 檢查其自訂類別、模組、載入路徑和後續指派。
  4. 只有後續值不同: 檢查兩種介面是否都收到更新後的模型值。
  5. 複數措辭錯誤: 檢查用來決定變化形式的數值參數,以及所有適用的變化形式。

Apple 說明了編譯器擷取設定,並指出原始碼中不再出現的已翻譯項目可能會標示為 Stale。這是清理的提示,不能證明資源已不再使用。刪除項目及其翻譯之前,請先檢查舊的介面指派和 Swift 呼叫端。探索字串目錄。

當兩個介面都從同一個定義取得相同且符合語境的訊息、參數保持完整,而且舊句子範本已無任何呼叫端時,整合工作便告完成。請將契約和驗收案例放在輔助程式旁邊,確保日後修改措辭時,兩個視窗共用的含義仍能保留。

來源