Firebase Translate text with ML Kit on iOSを使って、オフライン翻訳アプリを作る

## はじめに
Google I/O 2019 で発表された、`Firebase Translate text with ML Kit on iOS`がどんなものか確認するため、翻訳アプリを作って動作確認してみました。

【概要】

- 59カ国語をサポート
- 言語ごとの翻訳Modelをダウンロードし、ローカルで翻訳（オフライン翻訳ができる）

実装方法は、シンプルな機能なこともあり、とてもわかり易いFirebaseのドキュメントを見れば、すぐに機能を試すことができます。
今回は、実際の挙動やドキュメントに記載がない部分の確認を行いたいと思います。
[Firebase Translate text with ML Kit on iOS](https://firebase.google.com/docs/ml-kit/ios/translate-text)

## 作ったアプリ

翻訳元の言語と、翻訳する言語を選択、テキスト入力に合わせてインクリメンタルに翻訳を実行するようにしています。

![n_EUIfvNEfVoZUF1559458932.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113846/12e5a486-339a-9261-7972-68106f071fca.gif)　　![HMV9a_5LLWiNOJ91559459338.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113846/e6d5d223-1acd-4e8d-88f7-3cdb76cc6e34.gif)

翻訳したい言語のModelがダウンロード済みであればオフラインでも翻訳可能です。
![k0xVA0ndI7z8BiM1559459856.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113846/e53c25a1-a5bb-de29-14be-284ac2bcbb06.gif)



Githubで公開しています。適宜cloneして試してみてください。
https://github.com/iincho/SweTranslationNote
※GoogleService-Info.plistをプロジェクトに追加する必要があります。詳細はReadme、もしくはFirebaseドキュメントをご確認ください。

### 翻訳機能

何もコードを引用しないのも寂しいので、翻訳機能について抜粋します。
クロージャを使った非常にシンプルな構成です。

```swift
private func translate() {
        // 翻訳対象の言語と、翻訳先言語を指定した上で、Translatorオブジェクトを作成
        let options = TranslatorOptions(sourceLanguage: .JA, targetLanguage: .EN)
        let translator = NaturalLanguage.naturalLanguage().translator(options: options)
        
        // ダウンロードオプションを指定。ここで、モバイル回線でのDLの可否を指定することができる
        // Modelのサイズが非常に大きいため、使う状況で適切に切り替えるべき設定
        let conditios = ModelDownloadConditions(allowsCellularAccess: true, allowsBackgroundDownloading: true)
        
        // Modelがローカルになければダウンロードする処理
        // すでにダウンロード済みであればすぐにクロージャ内部の処理が実行される。
        // ダウンロードに時間ががかかるため、ローディング等の表示を行うと良いでしょう。
        translator.downloadModelIfNeeded(with: conditios) { error in
            guard let sSelf = self else { return }
            if let error = error {
                print("Download Error: \(error.localizedDescription)")
                return
            }
            
            // Modelがダウンロード済みであることが確認できたところで、翻訳実行
            // クロージャで翻訳後の文字列を受け取れる
            translator.translate("翻訳する文字列") { translatedText, error in
                guard error == nil, let translatedText = translatedText else {
                    print("Translation Error: \(String(describing: error))")
                    return
                }
                print("translatedText: \(translatedText)")
            }
        }
    }
```

オフライン翻訳を行うには、まずオンライン状態で翻訳Modelをローカルにダウンロードする必要があります。
ダウンロードするには２つ方法があり、翻訳実行時に必要な場合Modelをダウンロードするメソッド `func downloadModelIfNeeded(completion:)` か、個別にModelをダウンロードするメソッド `func download(_ remoteModel:) -> Progress` のどちらかを使います。

翻訳実行時は、`func downloadModelIfNeeded(completion:)`を使うことで、わざわざローカルModelの存在確認をする必要がなくなります。
`func download(_ remoteModel:) -> Progress` は事前にModelをダウンロードしておきたい場合に使うと良さそうです。
今回実装したアプリでは、別画面を用意し、言語の翻訳Modelのダウンロード、削除ができるようにしています。
![Q1VDS0mM5aXMsKz1559459018.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113846/3a6184b9-a713-d6da-9800-6c88ef03e03e.gif)

## 気になるところ

### 翻訳の実行スピード

翻訳Modelがローカルにない状態での翻訳スピードは、まず翻訳Modelをダウンロードする必要があるため遅いです。しかし、ダウンロード済みの場合、とても高速に翻訳されます。実際に触った感触では1秒かからずに翻訳結果が表示されます。

また、`英語→他言語` `他言語→英語`が高速に翻訳されるのに対し、英語以外の言語間の翻訳は、ワンテンポ遅い気がします。
おそらく一度英語に翻訳してから、対象の言語へ翻訳しているためだと思われます。

翻訳Modelのサイズはそこそこあります。１つあたり数十MB近いサイズ(言語により前後している)です。
モバイル通信で気軽にダウンロードするには気が引けるサイズなため、ダウンロードを実行する際にモバイル通信を使用するか設定があるので適宜切り替えておくと良さそうです。

試しに59カ国語すべてダウンロードすると`2.5GB`ほどになりました。単純に割ると1言語あたり`42MB`ある計算になります。
![IMG_1825.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113846/4a4e259f-12bf-f561-5ee4-93b945b9142a.png)

### ダウンロードしたModelの生存期間

試した限り、アプリを削除しない限りModelを保持し続けているようです。
バックグラウンドへの移行や、タスクキル、iPhoneの再起動した場合でもModelは残り続けます。
(期間については実装して間もないため、検証できていません。)

### ダウンロードの進捗表示

Modelダウンロードを行うメソッドの戻り値は`Progress`となっています。
`func download(_ remoteModel: FIRRemoteModel) -> Progress`

この`Progress`の`keyPath "fractionCompleted"`を監視することで進捗表示ができると思いきや、現状0.0, 1.0 のみ通知され間がありません。実装方法が良くないのかもしれません。

### 翻訳精度について

Web版のGoolge翻訳と比較した場合、翻訳結果に差が出ます。個人的にはそこそこ意味が通じる翻訳ができている印象です。
また、翻訳時に句読点を入れるかどうかで、結果が大きく変わるのも印象的です。

### メモリー使用量

~~私のアプリでは、起動時の段階で`１００MB`近く使用します。~~
~~また、Firebase側のバグなのかは不明ですが、言語を切り替えて再翻訳するたびに、メモリー使用量が`１００MB`単位で増加します。~~
~~なお、Firebaseが提供するサンプルアプリも確認しましたが同じ症状でした。~~
https://github.com/firebase/quickstart-ios/tree/master/translate

~~現状のままでは、残念ながらプロダクトへの導入はできません。~~

Firebase関連Frameworkの更新を行ったところ、改善しました。
差分を確認すると、`FirebaseMLNLTranslate' が 0.16.0 -> 0.16.1 に更新されていました。
[Podfile.lockの差分](https://github.com/iincho/SweTranslationNote/commit/f972249dde4950f4633ef3cc70c70077ab277398#diff-04fcbd1c0da95c817cfec1af86aa32adR11)

最新のバージョンを使うことで、メモリ使用量は35MB前後となり実用に耐える状態です。
言語変更後の翻訳でもメモリー使用量が増えることなく安定して使えるようになりました。

## 最後に

翻訳精度がそこまで高くないものの、翻訳Modelがダウンロード済みであれば高速に翻訳できるのは非常に魅力的です。
以下のようなサービスはサーバー側の改修なしに多言語化することができるので、導入する価値があると思います。

- チャット欄等のリアルタイム翻訳（チャットアプリや、オンラインゲームのチャットなど）
- CGMで作成したコンテンツ（口コミ等）の翻訳

例えばTwitterのタイムラインの英語が常にはじめから翻訳付きになったら、個人的にとても嬉しいです。