WWDC Quick Look 💓 By SwiftGGTeam
Meet the Translation API

Meet the Translation API

元の動画を見る

ハイライト

Apple は Translate app のデバイス上 ML モデルを開発者に公開し、.translationPresentation() システムオーバーレイと柔軟な TranslationSession API の 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)。


重要ポイント

  1. まず .translationPresentation() で需要を検証し、深い統合には TranslationSession を使用

    • 価値がある理由:システムオーバーレイはゼロコスト統合。翻訳の価値を素早く検証——ユーザーがほとんど使わなければ UI 統合に投資する価値なし
    • 始め方:翻訳が必要な view に .translationPresentation() を追加。メニューボタンでトリガー。使用頻度を観察
  2. オフラインシナリオ向けに言語パックを事前ダウンロード

    • 価値がある理由:翻訳は屋外旅行や地下鉄通勤など弱ネットワークで最も価値がある。事前ダウンロードで体験を中断しない
    • 始め方:オフラインモードや不安定なネットワークを検出したら prepareTranslation() でダウンロード承認を要求
  3. 言語ごとにバッチ翻訳

    • 価値がある理由:異なる言語を 1 バッチに混ぜると文字化け。言語ごとにバッチ分けで翻訳品質を保証
    • 始め方:NLLanguageRecognizer またはフレームワークの自動検出で各テキストの言語を検出。言語でグループ化。各グループで session.translate() を個別呼び出し

関連セッション

コメント

GitHub Issues · utterances