ハイライト
Apple は Translate app のデバイス上 ML モデルを開発者に公開し、
.translationPresentation()システムオーバーレイと柔軟なTranslationSessionAPI の 2 層を提供。
主要内容
汎欧州ハイキング app を作ったとする。ユーザーレビューはドイツ語、日本語、オランダ語かもしれない——ローカライズだけでは不十分。ユーザー生成コンテンツの言語は制御できない。以前はサードパーティ翻訳サービスを統合するか、ユーザーにテキストをシステム翻訳 app にコピーして戻ってもらう必要があった。
iOS 18 では Apple が Translate app 背後のデバイス上 ML モデルを Translation フレームワークで公開。第一層は .translationPresentation()——SwiftUI modifier で 1 行追加するだけで app 内にシステム翻訳オーバーレイを表示。システム全体の翻訳と同じ体験。第二層は TranslationSession——より柔軟な async API。翻訳結果を取得して表示方法を自分で決定。バッチ翻訳、言語検出、自動言語選択をサポート。
両 API は同じデバイス上 ML モデルを共有。言語パックはシステムに既に存在(ユーザーがダウンロード済み)。app は直接使用可能。ユーザーが言語をダウンロードしていない場合、フレームワークが自動でダウンロードプロンプトを表示。バックグラウンドでダウンロード。ユーザーが app を離れても影響なし。今年はヒンディー語が追加され、Translate app と同じ言語リストをカバー。
詳細
システムオーバーレイ翻訳は「たまに翻訳する」シナリオ向け——ユーザーが理解できないテキストに遭遇し、タップして結果を見れば十分。実装は view に .translationPresentation() modifier を追加し、state 変数で表示を制御(03:00):
// 翻訳オーバーレイを制御する state を保存
@State private var showsTranslation = false
// コンテキストメニューに翻訳ボタンを追加
ContextMenu {
Button("Translate") {
showsTranslation = true
}
} // ([02:51-03:09](https://developer.apple.com/videos/play/wwdc2024/10117/?time=171))
// view に translationPresentation modifier を追加
.translationPresentation(isPresented: $showsTranslation, text: reviewText)
キーポイント:
@State private var showsTranslation = false— 翻訳オーバーレイの表示 state を保存showsTranslation = true— ユーザーが翻訳ボタンをタップしたとき true に設定し、オーバーレイをトリガー.translationPresentation(isPresented:text:)— isPresented が true のとき自動でシステム翻訳オーバーレイを表示。text は翻訳対象
この実装は非常に軽量。ユーザーは自分でターゲット言語を切り替え可能。ただし複数の翻訳を同時表示(レビューリスト全体など)したり、翻訳結果を自分の UI に埋め込む場合は TranslationSession が必要。
TranslationSession は async API——直接インスタンス化できない。.translationTask() modifier からフレームワークがインスタンスを提供(04:30):
// 翻訳 session の設定を保存
@State private var configuration: TranslationSession.Configuration?
// view に translationTask modifier を追加
.translationTask(configuration) { session in
// 設定が変わるとこのクロージャが呼ばれる
let requests = filteredReviews.map { review in
TranslationSession.Request(text: review.text)
}
// バッチ翻訳。結果はストリーミングで返る
for await result in session.translate(requests) {
switch result {
case .success(let translation):
updateReview(with: translation)
case .failure(let error):
handleError(error)
}
}
}
キーポイント:
@State private var configuration— 翻訳設定を保存。nil のとき翻訳はトリガーされない.translationTask(configuration:)— configuration が nil から非 nil に変わると、フレームワークがクロージャを呼び session を渡すTranslationSession.Request(text:)— 翻訳対象テキストをラップsession.translate(requests)— AsyncSequence を返し、結果はストリーミングfor await result in— 各結果を処理。成功時は UI 更新、失敗時はエラー処理
翻訳をトリガーするロジックは configuration に新しい値を代入(06:48):
if configuration == nil {
// 初回翻訳:デフォルト設定(ソース言語自動検出、ターゲット自動選択)
configuration = TranslationSession.Configuration()
} else {
// 再翻訳:invalidate() でコンテンツ変更をフレームワークに通知
configuration?.invalidate()
}
キーポイント:
configuration == nilで初回翻訳かどうかを判定TranslationSession.Configuration()デフォルト初期化子は言語を自動選択configuration?.invalidate()で SwiftUI に設定変更を通知し、translationTask クロージャを再実行
言語選択ではソース言語とターゲット言語を手動指定するか、nil を渡してフレームワークに任せる(09:00):
// 言語を手動指定
TranslationSession.Configuration(
source: Locale.Language(identifier: "es"),
target: Locale.Language(identifier: "en")
)
// ソース言語自動検出、ターゲット自動選択
TranslationSession.Configuration(
source: nil, // コンテンツ言語を自動検出
target: nil // ユーザー優先言語に基づき自動選択
)
キーポイント:
source: nil— フレームワークがテキスト言語を自動識別。識別できない場合はユーザーに確認target: nil— ソース言語とユーザー優先言語に基づきターゲット言語を自動選択
言語検出について、フレームワークは NLLanguageRecognizer を自分で呼ぶより TranslationSession に nil を渡すことを推奨(10:03)。単独検出が必要な場合:
let recognizer = NLLanguageRecognizer()
recognizer.processString(text)
if let dominantLanguage = recognizer.dominantLanguage {
let language = Locale.Language(identifier: dominantLanguage.rawValue)
// language を TranslationSession に渡す
}
言語サポート状況は LanguageAvailability API で確認(11:53):
// 翻訳をサポートする全言語を取得
let supportedLanguages = LanguageAvailability.supportedLanguages
// 言語ペアが翻訳をサポートするか確認
let status = LanguageAvailability.status(
from: Locale.Language(identifier: "en"),
to: Locale.Language(identifier: "hi")
)
switch status {
case .supported:
print("Supported")
case .installed:
print("Supported and downloaded")
case .unsupported:
print("Unsupported")
}
キーポイント:
supportedLanguages— 翻訳をサポートする全言語リスト。Translate app と一致status(from:to:)— ソースからターゲットへの翻訳がサポートされるか確認.installedは言語ペアが翻訳をサポートし、言語パックがデバイスにダウンロード済み
バッチ翻訳の重要な制約:同一バッチ内の全テキストは同じ言語である必要がある(13:37)。ドイツ語とスペイン語を混ぜると文字化け。正しい方法は言語ごとにバッチ分け:
// 誤り:異なる言語を 1 バッチに混在
let mixedRequests = [
TranslationSession.Request(text: germanText1),
TranslationSession.Request(text: spanishText1)
]
// 正しい:別バッチで翻訳
for await result in session.translate(germanRequests) {
// ドイツ語翻訳結果を処理
}
for await result in session.translate(spanishRequests) {
// スペイン語翻訳結果を処理
}
言語ごとにバッチ分けする場合、session のソース言語は nil に設定。フレームワークが各バッチで言語識別できるように。ユーザーに言語パックのダウンロードを事前承認させたい場合(オフライン翻訳など)、prepareTranslation() を呼ぶ(14:50):
try await session.prepareTranslation(
from: Locale.Language(identifier: "en"),
to: Locale.Language(identifier: "ja")
)
最後に TranslationSession のライフサイクル——view にバインドされ、view が消えると session は使用不可。session を永続化モデルオブジェクトに保存しない(15:13)。
重要ポイント
-
まず
.translationPresentation()で需要を検証し、深い統合にはTranslationSessionを使用- 価値がある理由:システムオーバーレイはゼロコスト統合。翻訳の価値を素早く検証——ユーザーがほとんど使わなければ UI 統合に投資する価値なし
- 始め方:翻訳が必要な view に
.translationPresentation()を追加。メニューボタンでトリガー。使用頻度を観察
-
オフラインシナリオ向けに言語パックを事前ダウンロード
- 価値がある理由:翻訳は屋外旅行や地下鉄通勤など弱ネットワークで最も価値がある。事前ダウンロードで体験を中断しない
- 始め方:オフラインモードや不安定なネットワークを検出したら
prepareTranslation()でダウンロード承認を要求
-
言語ごとにバッチ翻訳
- 価値がある理由:異なる言語を 1 バッチに混ぜると文字化け。言語ごとにバッチ分けで翻訳品質を保証
- 始め方:
NLLanguageRecognizerまたはフレームワークの自動検出で各テキストの言語を検出。言語でグループ化。各グループでsession.translate()を個別呼び出し
関連セッション
- Bring your app to Siri — App Intents で翻訳機能を Siri に公開。「このレビューを翻訳して」と言える
- Bring your app’s core features to users with App Intents — App Intents でシステム検索とショートカットから翻訳機能を呼び出し
- What’s new in Foundation — Locale.Language など Foundation 言語処理 API
- Integrate your app with Productivity broadcasts — ブロードキャストで翻訳タスクなどの自動化シナリオをトリガー
コメント
GitHub Issues · utterances