iOS 18 Translation API を自由に使う方法

　iOS 18 では、Apple が提供する新しい Translation API によって、アプリ内で翻訳機能を簡単に実装できるようになりました。

公式デモコード：

https://developer.apple.com/documentation/translation/translating-text-within-your-app#Offer-a-custom-translation
<br><br>

 完璧そうなサンプルコードでしたが...
```swift
@State private var configuration: TranslationSession.Configuration?

.translationTask(configuration)

func triggerTranslation()
```

これらのコードをセットでViewに書く必要があります。
複数の画面で翻訳したい、モジュール化したいなどの時はそのまま使えません。

 <br>
 公式のデモでは、固定されたスタイルで使う例が紹介されていますが、実際のアプリではもっと柔軟に翻訳機能を利用したい場合が多いでしょう。

　この記事では、任意の場所で自由に Translation API を活用する方法を、実際のコードを交えながら解説します。

### 基本的な構造
Translation API を任意の場所で使うために、以下のポイントを抑える必要があります：
 - TranslationSession.Configuration を動的に管理：公式デモの固定設定ではなく、柔軟に変更可能な設定を使う。
 - 任意のテキストを翻訳：各画面やコンポーネントごとに異なるソーステキストを翻訳。
 - 結果を非同期で取得：async/await やコールバックを活用して、翻訳結果を取得し、UI を更新。

以下のコード例では、これらを満たす仕組みを作成しています。

### 実装コード例
まずは、`TranslationRelay` クラスを作成し、Translation APIの管理を集中化します。

`TranslationRelay` の実装

```swift
@Observable
class TranslationRelay {
    static var shared = TranslationRelay()
    var configuration: TranslationSession.Configuration?
    var sourcetext: String = ""
    private var translationCallback: ((String) -> Void)?
    
    private init() {}
    
    /// TranslationSession の設定を初期化
    func translationConfigure() {
        configuration = .init()
        // 必要に応じてカスタム設定を行う
        // configuration = .init(source: Locale.Language(identifier: "ja"), target: Locale.Language(identifier: "en"))
    }
    
    /// 翻訳をトリガーし、結果をコールバックで返す
    func triggerTranslation(sourceText: String, callback: @escaping (String) -> Void) {
        sourcetext = sourceText
        guard configuration == nil else {
            configuration?.invalidate()
            return
        }
        translationConfigure()
        translationCallback = callback
    }
    
    /// 翻訳結果を受け取り、コールバックを呼び出す
    func accept(result: String) {
        translationCallback?(result)
    }
}
```

`View` 拡張での `withTranslation` の活用
`withTranslation` を使えば、任意の View で簡単に翻訳タスクを実行できます。

```swift
extension View {
    func withTranslation() -> some View {
        self.translationTask(TranslationRelay.shared.configuration) { session in
            do {
                let response = try await session.translate(TranslationRelay.shared.sourcetext)
                TranslationRelay.shared.accept(result: response.targetText)
            } catch {
                print("翻訳に失敗しました: \(error)")
            }
        }
    }
}
```

### 実際の画面での使用例
それぞれの画面で異なるテキストを翻訳し、結果をリアルタイムに取得します。

```swift
struct DemoApp: App {
    var body: some Scene {
        WindowGroup {
            NavigationStack {
                VStack {
                    NavigationLink("TranslationView1", destination: TranslationView1())
                    NavigationLink("TranslationView2", destination: TranslationView2())
                }
            }
            .withTranslation()
        }
    }
}

struct TranslationView1: View {
    var body: some View {
        Button("Translate") {
            TranslationRelay.shared.triggerTranslation(sourceText: "Bonjour le monde!") { targetText in
                print("翻訳結果: \(targetText)")
            }
        }.tint(Color.red)
    }
}

struct TranslationView2: View {
    var body: some View {
        Button("Translate") {
            TranslationRelay.shared.triggerTranslation(sourceText: "Hallo, Welt!") { targetText in
                print("翻訳結果: \(targetText)")
            }
        }.tint(Color.black)
    }
}

```

### このコードのポイント
 - TranslationRelay を使った集中管理
 - 柔軟な翻訳のトリガー
 - withTranslation で簡潔なタスク管理

### 終わりに
iOS 18 の Translation API は非常にパワフルですが、公式デモの使い方にとらわれる必要はありません。本記事で紹介した方法を参考に、自由な翻訳体験をアプリに取り入れてみてください！

質問や改善案があれば、ぜひコメント欄でお知らせください 😊