再利用可能な macOS ラベルでは、ローカライズ可能なメッセージを LocalizedStringResource として受け取り、リテラルテキストには別途 verbatim: の経路を用意し、ローカライズされるまで補間を保持します。このチュートリアルでは、デスクトップ項目カウンター向けにこの契約を実装し、抽出と表示を別々に確認します。
再利用可能なテキスト API が保証すべきこと
Mac アプリに、項目数と Sample Set のような仮のコレクション名を表示する簡潔な概要が必要だとします。再利用可能なコンポーネントでは、入力を次の3種類に区別する必要があります。翻訳が必要なアプリ作成のメッセージ、既存の SwiftUI ローカライズキー、そして渡されたとおりに正確に表示するテキストです。
Apple は、カスタムビューやその他の Swift コードを通じてローカライズ可能な文字列を表現して渡す方法として、LocalizedStringResource を推奨しています。現在のガイダンスでは、この型のカスタムビューのプロパティを Text で表示する例が示されています。新しいコンポーネントを設計する際は、これをメッセージの基本契約として使いましょう。Preparing your app’s text for translation。
次の判断表は、文書化された動作をもとにした API 設計案です。引数ラベルを明示すれば、ラベルのない文字列オーバーロードを二つ用意して競合させずに、呼び出し側の意図を確認できます。
| 呼び出し側の意図 | 推奨するエントリーポイント | 表示経路 |
|---|---|---|
| 任意のコンテキストを添えて翻訳可能なメッセージを渡す | SummaryLabel(localized:) |
Text(resource) |
| SwiftUI の補間を含む SwiftUI キーを渡す | SummaryLabel(key:) |
Text(key, tableName:bundle:) |
| ラベルをそのまま表示する | SummaryLabel(verbatim:) |
Text(verbatim:) |
| 呼び出し側ですでに組み立てたテキストを渡す | SummaryLabel(text:) |
渡された Text を保持する |
コンポーネントで SwiftUI のキー方式が必要な場合は、LocalizedStringKey も使えるようにしておきます。Apple の説明によると、文字列リテラルを Text に直接渡すとローカライズキーが作られますが、String 変数は通常、ローカライズを経由しない経路になります。そのため、String だけを受け取るラッパーでは、見た目が似た呼び出しの意味が変わってしまいます。LocalizedStringKey。
明示的なエントリーポイントを備えたコンポーネントを作る
この例では、デプロイメントターゲットを macOS 26 に設定した Xcode 26 以降の macOS SwiftUI アプリプロジェクトを使います。生成された app と content view の宣言を以下の完全なプログラムに置き換え、ターゲット内の @main 宣言は一つだけにしてください。ラベルと数値はすべて仮のものです。コードは文書化された API と照らして確認しましたが、この環境には Swift コンパイラーがないため、コンパイルはしていません。
import Foundation
import SwiftUI
struct SummaryLabel: View {
private let text: Text
init(localized resource: LocalizedStringResource) {
text = Text(resource)
}
init(
key: LocalizedStringKey,
tableName: String? = nil,
bundle: Bundle? = nil,
comment: StaticString? = nil
) {
text = Text(
key,
tableName: tableName,
bundle: bundle,
comment: comment
)
}
init(verbatim value: String) {
text = Text(verbatim: value)
}
init(text: Text) {
self.text = text
}
var body: some View {
text
.font(.callout)
.padding(8)
}
}
struct ContentView: View {
@State private var itemCount = 2
private let collectionLabel = "Sample Set"
var body: some View {
VStack(alignment: .leading, spacing: 12) {
Stepper(value: $itemCount, in: 0...12) {
Text("Item count")
}
SummaryLabel(
localized: LocalizedStringResource(
"summary.item-count",
defaultValue: "\(itemCount) items",
comment: "Summary below the item-count control. The integer is the number of items."
)
)
SummaryLabel(
key: "Collection: \(collectionLabel)"
)
SummaryLabel(verbatim: collectionLabel)
SummaryLabel(
text: Text(
"Preview",
comment: "Heading for a preview of the current item selection."
).bold()
)
}
.padding(20)
.frame(minWidth: 320)
}
}
@main
struct TextContractDemoApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
resource イニシャライザーでは、安定したキー summary.item-count とデフォルトメッセージ \(itemCount) items を意図的に分けています。Apple の説明では、このイニシャライザーに静的キー、ローカライズ可能なデフォルト値、テーブル、ロケール、バンドル、翻訳者向けコメントを指定できます。指定したテーブルにキーがない場合は、デフォルト値が使われます。LocalizedStringResource initializer。
verbatim: イニシャライザーは、渡された文字列をローカライズせずに表示するという、明確な保証を提供します。リテラル引数にも変数にも使えます。正確な綴りそのものを表示したい場合は、仮のコレクション名にこの経路を使いましょう。Text.init(verbatim:)。
text: エントリーポイントを使えば、呼び出し側でローカライズ済みまたはスタイル設定済みのテキストを作ってから渡せます。このコンポーネントは共通のタイポグラフィと間隔を追加し、メッセージの意味は呼び出し側が選びます。Text の受け渡しはエスケープハッチです。より限定的な契約を強制する必要があるコンポーネントでは、このイニシャライザーを省略できます。
抽出と実行時の書式設定はどう違うか
ローカライズは二つの別々の確認として扱います。ビルド時には、Xcode がローカライズ可能なソース式を検出して String Catalog を更新します。実行時には、アプリがメッセージを解決し、引数を置き換えます。Apple の String Catalog セッションでは、カスタムビューの呼び出し箇所で LocalizedStringResource をインスタンス化するために使われているリテラルを認識する方法が説明されています。また、Use Compiler to Extract Swift Strings ビルド設定も紹介されています。Discover String Catalogs。
補間は、通常の完成済み文字列になる前に、ローカライズ対応の型に渡す必要があります。Apple の説明によると、SwiftUI の補間ビルダーは書式キーと引数の値を分けて保持するため、置換前にローカライズできます。補間済みの String をキーに変換しても、元の型付き引数は復元できません。What’s New in Swift。
次の診断用コード断片は、この違いを示すものです。二つ目のアプリではありません。断片で使う値はすべてローカルに定義されています。
import SwiftUI
let count = 2
// Ordinary String interpolation has already produced "2 items".
let completed: String = "\(count) items"
let exactDisplay = Text(completed)
// Looks up the completed value as a key.
// This does not restore a count placeholder.
let completedValueLookup = Text(LocalizedStringKey(completed))
// Preserves localization-aware interpolation from the beginning.
let messageKey: LocalizedStringKey = "\(count) items"
let interpolatedDisplay = Text(messageKey)
変数から LocalizedStringKey を作ることは、実行時の検索を要求する方法として文書化されています。変数に既知のキーを意図的に格納している場合に便利です。ただし、その変数が取り得るすべての値を Xcode がカタログに抽出したことを保証するものではありません。意図的な動的キーは明示的に管理するか、アプリが持つ有限個のメッセージにはリテラルのリソース宣言を使いましょう。検索の動作とフォールバックについては、Text.init(_:tableName:bundle:comment:) に説明があります。
句読点とプレースホルダーの位置も含め、メッセージ全体を一つのローカライズ可能な式に保持します。この例では、Collection: \(collectionLabel) を分割せずにまとめて扱います。解決済みの断片を組み合わせて完成メッセージを作ると、順序と空白の管理が呼び出し側の責任になります。
カタログを追加して複数形の動作を設定する
まず、File > New > File from Template から String Catalog を追加し、String Catalog を選んで Localizable という名前を付けます。次に Product > Build を選択します。Apple の説明によると、ビルドによってローカライズ可能な API からカタログが作られ、ソースメッセージの変更に応じて更新されます。Localizing and varying text with a string catalog。
このサンプルでは、summary.item-count のエントリーと補間された整数を確認します。エントリーを Control キーを押しながらクリックし、Vary by Plural を選びます。英語のソースローカライズでは、Xcode が用意する整数プレースホルダーを残したまま、One を %lld item、Other を %lld items に設定します。ほかのローカライズを追加する際は、必要な形式も追加してください。複数形のカテゴリーは言語によって異なります。Localizing and varying text with a string catalog。
補間で個数は渡せますが、それだけで単数形の文言が作られるわけではありません。Apple のコード解説では、カタログに複数形のバリエーションを追加し、実行時にシステムが一致するバリエーションを選ぶ方法が示されています。したがって、ビルドが成功しメッセージが抽出されたことは、検証の一部にすぎません。Code-along: Explore localization with Xcode。
ソース言語の複数形を設定したら、次の簡単な受け入れ確認表を使います。以下は説明用プログラムで期待される結果であり、実行結果の報告ではありません。
| 確認項目 | 期待される結果 |
|---|---|
ビルドして summary.item-count を確認する |
整数置換を含むメッセージが一つ登録される |
個数を 0 に設定する |
0 items |
個数を 1 に設定する |
1 item |
個数を 2 に設定する |
2 items |
| 正確なラベルの行を確認する | Sample Set がリテラルのまま表示される |
| コレクションのメッセージを確認する | ラベルがメッセージ全体の引数として扱われる |
カタログのエントリー不足と表示の誤りを別々に診断する
カタログにメッセージがない場合は、まずソースの型を確認します。String パラメーターによって、コンポーネントに届く前にローカライズ可能な式が失われている可能性があります。カタログのターゲット、呼び出し箇所を含むスキームをビルドしているか、そして Use Compiler to Extract Swift Strings の設定を確認してください。Apple によると、抽出は現在のスキームとプラットフォームを対象とするため、メッセージを実際にコンパイルする構成を確認します。Discover String Catalogs。
エントリーがあるのにアプリにキーやフォールバックテキストが表示される場合は、検索設定を確認します。キーを使う Text イニシャライザーは指定されたテーブルとバンドルを検索します。省略した場合は、デフォルトのテーブルとメインバンドルが選ばれます。キーまたはテーブルが見つからないと、キーの文字列表現が表示されます。そのため、読みやすいフォールバックが不完全な検索を覆い隠すことがあります。Text.init(_:tableName:bundle:comment:)。
コンポーネントをフレームワークやパッケージに移す場合は、メッセージの格納先を決めます。コンポーネントが所有するメッセージには、そのコンポーネントのリソースバンドルが必要です。呼び出し側が所有するリソースでは、渡された検索情報を維持します。Apple の現行ガイダンスでは、#bundle をローカライズ API に渡して、含まれているターゲットのバンドルを指定する方法が推奨されています。名前付きテーブルにも、対応するカタログが必要です。Preparing your app’s text for translation。
最後に、SwiftUI で受け付けられる補間がすべて Foundation のリソースでも使えるとは限りません。LocalizedStringKey には、書式指定やフォーマッター用など、独自の補間メソッドがあります。式を組み立てる前にメッセージの型を選び、引数でコンパイルエラーが起きた場合は、その型でサポートされる補間操作を確認してください。LocalizedStringKey.StringInterpolation。
呼び出し側が意図を明確に表せて、二つの段階で検証できれば、再利用可能なテキストコンポーネントの準備は完了です。ビルドによって意図したカタログメッセージが生成され、実行中の Mac アプリに正しい置換結果、複数形、または指定どおりのラベルが表示されることを確認します。コンポーネントを変更するたびに、これらの確認を API 契約とあわせて維持してください。
出典
- Preparing your app’s text for translation — Apple
- LocalizedStringKey — Apple
- init(_:defaultValue:table:locale:bundle:comment:) — Apple
- init(verbatim:) — Apple
- Discover String Catalogs — Apple
- What’s New in Swift — Apple
- init(_:tableName:bundle:comment:) — Apple
- Localizing and varying text with a string catalog — Apple
- Code-along: Explore localization with Xcode — Apple
- LocalizedStringKey.StringInterpolation — Apple