Macアプリ内で繰り返し使われるメッセージを一元化するには、共通する意味ごとにローカライズキーを1つ、Swift定義を1つ用意し、そのメッセージが表示されるすべての場所で使います。引数、複数形のルール、翻訳者向けのコンテキストをまとめて管理してください。英語のテキストが一致するだけでは不十分です。
この手順は、SwiftUIとInterface Builderのウインドウの両方で選択項目の概要を管理するMac開発者を対象としています。ここで扱う具体的な作業は、XIBとSwiftコードで別々の文テンプレートを保守せずに、2 items selected in Batch A.のようなメッセージを一貫して表示することです。以下の件数とラベルはすべてサンプルです。
どの繰り返しメッセージをまとめるか決める
カタログを編集する前に、メッセージの契約を定義します。文の意味、受け取る値、表示される場所を明確にしてください。この例の契約は次のとおりです。
| 契約の要素 | 定義 |
|---|---|
| 意味 | 名前付きコレクション内で現在選択されている項目数 |
| キー | selection.summary |
| テーブル | Localizable |
| バンドル | メインアプリバンドル |
| 引数1 | Int64型の選択数 |
| 引数2 | Batch Aなどのリテラルなコレクションラベル |
| 変化形 | 選択数に基づく複数形 |
| コンテキスト | SwiftUIとAppKitのラベルに表示する完結したステータス文 |
この契約を、一元化できるかどうかの判定基準にしてください。意味、引数、文法上の役割が一致すれば、2つの出現箇所で同じメッセージを共有できます。選択項目数と全項目数は、どちらも現在は2 itemsと表示されるとしても、別々にしておくべきです。Selectというボタンにも動作を表すメッセージが必要なため、独自の定義が必要です。
Appleは、テキストの意味を区別できるよう、既定値を分けた明示的なキーをサポートしています。これにより、読みやすい元の文言を保ちながら、用途に基づいてメッセージに名前を付けられます。コメントがコンテキストを示し、キーがエントリを識別します。Stringの明示的なキーを使うイニシャライザ。
テーブルとバンドルも検索契約の一部として扱ってください。別のテーブルにあるキーは、別の検索対象です。Appleのドキュメントでは、tableまたはtableNameパラメータによるテーブルの指定が説明されており、ローカライズ可能なテキストをLocalizedStringResource経由で渡すことが推奨されています。この例では、ヘルパーとカタログをアプリターゲットに置き、メインバンドルを使います。アプリのテキストを翻訳用に準備する。
抽出と実行時フォーマットの境界を理解する
抽出はXcodeのビルド中に行われます。Xcodeは対応するローカライズ可能な式を見つけ、カタログ内のソーステキストと同期します。実行時のローカライズでは、アプリがメッセージを解決し、実際の値を代入します。カタログにエントリがあることは、抽出でそのエントリが見つかった証拠にはなりますが、すべてのコントロールに正しく表示されることの証明にはなりません。
Interface Builderには独自のリソースワークフローがあります。Appleのドキュメントでは、ストーリーボードまたはXIBと組み合わせたカタログが説明されています。一方、Swiftソースは通常Localizableテーブルに追加されます。公開されているワークフローには、表示される文言が一致するだけでメッセージを統合する、リソースをまたいだエイリアスは記載されていません。対応している実用的な方法は、インターフェイスの読み込み後にSwiftから共通のローカライズ済みメッセージを設定することです。これはAppleの公開APIをもとに導いた実装方法です。String Catalogの概要。
SwiftUIでも、リテラルなローカライズキーと通常の文字列値は区別されます。たとえば、Text("Summary")はローカライズを要求しますが、String変数を渡すと検索は行われず、その値が表示されます。実行時の文字列をLocalizedStringKeyでラップすれば検索を要求できますが、それは取り得る値が抽出時に見つかったことの証明とは異なります。LocalizedStringKey。
そのため、以下の設計では、抽出可能なリテラルを1つのヘルパー内に保ちます。件数とラベルをLocalizedStringResourceに格納し、完成した文字列が必要な場所でリソースを解決します。Appleはこのリソース型を、ローカライズの検索を遅延させる方法として説明しています。macOS 13以降で利用でき、この例の最小ターゲットです。LocalizedStringResource。
共通メッセージをSwiftで一度だけ定義する
SwiftUIを使うmacOSアプリプロジェクトを作成し、デプロイメントターゲットをmacOS 13以降に設定して、Localizable.xcstringsをアプリターゲットに追加します。現行のXcodeドキュメントに記載された作成手順は、File > New > File from Templateを選び、stringを検索してString Catalogを選択する方法です。String Catalogを使ったテキストのローカライズと変化形の設定。
生成されたアプリのエントリポイントを、以下の完全なプログラムに置き換えます。ターゲットに@main宣言が1つだけ含まれるよう、元のアプリのエントリポイントを削除してください。SwiftUIウインドウはXIBとは独立して動作します。次のセクションで既存のInterface Builderラベルを適応させる際に、ここに含まれるAppKitサブクラスを使います。
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()
}
}
}
静的キーがメッセージを識別し、補間された既定値がソース文と実行時引数を指定します。このイニシャライザでは、テーブル、バンドル、翻訳者向けコメントも指定できます。既定のバンドルはメインバンドルです。LocalizedStringResourceの明示的なキーを使うイニシャライザ。
ラベルBatch Aは文に挿入されるサンプルコンテンツであり、別のメッセージキーではありません。この区別により、コレクションのラベルを、その周囲にあるインターフェイスの文言から切り離せます。実際のアプリで定義済みの翻訳対象コレクション名を使う場合は、ここに渡す前に、それぞれ独自の定義でローカライズしてください。
件数、items selected in、ラベルなど、個別にローカライズしたパーツを組み立てて文を作らないでください。文全体を契約に含めれば、ローカライズ担当者は語句や引数の順序をまとめて調整できます。
Interface Builderのラベルも同じ定義を使う
既存のXIBまたはストーリーボードでは、概要表示に静的なNSTextFieldラベルを使います。カスタムクラスをSelectionSummaryFieldに設定し、モジュールにはアプリターゲットを指定します。このラベルは共通のSwift定義からテキストを受け取るため、デザイン時の文字列値から古い文を削除してください。
インターフェイスの読み込み時に、サブクラスはsuper.awakeFromNib()を呼び出し、サンプルの初期概要を設定します。Appleのドキュメントでは、awakeFromNib()は読み込み後のフックであり、その時点ではアウトレットとアクションの接続が確立されていると説明されています。また、スーパークラスの実装を呼び出す必要があります。awakeFromNib()。
実際のウインドウでは、選択が変わるたびに、SwiftUIに渡しているものと同じモデル値を使って、接続済みのラベルにupdateSelection(count:collectionLabel:)を呼び出します。サンプルの初期値の設定を、ウインドウの実際の初期化処理に置き換えてください。読み込み時に一度だけ設定しても、その後の選択変更は追跡できません。
AppKitアダプターは、stringValueを設定します。これはNSTextFieldのテキストを設定する、ドキュメントに記載された方法です。これによりレイアウトはInterface Builderに残したまま、メッセージの管理をSwiftに移せます。文全体を表示できるよう、十分な幅を確保するか折り返しを有効にしてください。NSTextField。
関係のない静的なインターフェイスのテキストは、既存のリソースワークフローに残してください。この移行の対象は共通メッセージ1つであり、すべてのラベルをSwiftに移す必要はありません。また、バインディングや別のコントローラーによる設定が、後から概要を古い文言で上書きしないことも確認してください。
引数を変えずに複数形の変化形を設定する
Product > Buildを選び、Localizable.xcstringsを開いて、selection.summaryを見つけます。エントリをControlキーを押しながらクリックし、Vary by Pluralを選択します。Xcodeから変化対象の引数を尋ねられた場合は、数値の件数引数を選んでください。Appleのドキュメントでは、補間されたメッセージに対するこの手順が説明されています。String Catalogを使ったテキストのローカライズと変化形の設定。
英語のソースローカライズでは、単数形の変化形が1 item selected in Batch A.となり、その他の変化形が2 items selected in Batch A.のような文になるようにします。Xcodeが指定した2つの引数プレースホルダーは、どちらも保持してください。サンプルの件数やラベルでプレースホルダーを置き換えず、周囲の文言を編集します。
| サンプル入力 | 複数形の設定後に期待される英語の結果 |
|---|---|
件数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を設定します。もう一度アプリを実行し、両方の表示経路を確認してください。Appleのドキュメントでは、システム全体の言語を変更せずにローカライズをテストするための、これらのスキーム設定が説明されています。アプリの実行時にローカライズをテストする。
結果に問題がある場合は、次の順序で確認してください。
- エントリが見つからない: カタログがアプリターゲットに属していることを確認し、対象のスキームをビルドします。
Use Compiler to Extract Swift Stringsが有効になっているか確認してください。 - 予期せずソースの文言が表示される: 読みやすいフォールバックテキストが表示されたことを成功とみなす前に、キー、テーブル、バンドル、翻訳を確認します。
- Interface Builderのラベルだけが異なる: カスタムクラス、モジュール、読み込み経路、その後の再設定を確認します。
- 後から設定した値だけが異なる: 両方のインターフェイスにモデルの更新値が渡されているか確認します。
- 複数形の文言が誤っている: 変化対象に選んだ数値引数と、該当するすべての変化形を確認します。
Appleは、コンパイラによる抽出設定について説明しており、ソース内で見つからなくなった翻訳済みエントリがStaleと表示されたまま残る場合があるとしています。この状態は整理の目安ですが、リソースが使われていない証拠ではありません。エントリと翻訳を削除する前に、古いインターフェイスの設定処理とSwiftの呼び出し元を確認してください。String Catalogの概要。
両方のインターフェイスが1つの定義から同じコンテキストのメッセージを取得し、引数がそのまま保たれ、古い文テンプレートを使う箇所がなくなれば、一元化は完了です。今後の文言変更でも両方のウインドウで意味が保たれるよう、契約と受け入れケースをヘルパーの近くに置いてください。
出典
アプリのテキストを翻訳用に準備する — Apple
String Catalogの概要 — Apple
LocalizedStringKey — Apple
LocalizedStringResource — Apple
init(localized:defaultValue:table:bundle:locale:comment:) — Apple
NSTextField — Apple
awakeFromNib() — Apple
アプリの実行時にローカライズをテストする — Apple