Firebase Extensionsの翻訳機能はサービス終了したGoogleウェブサイト翻訳ツールの代わりになるか🤔

この記事は[くふうカンパニー Advent Calendar 2019](https://qiita.com/advent-calendar/2019/kufu)の19日目の記事となります。

[Da Vinci Studio](https://da-vinci-studio.com/)でWebエンジニアをしている@TAByasuです。
[くふうグループ](https://kufu.co.jp/about/info/products)内の組織であるDa Vinci Studioではグループ内外の受託開発・デザインを生業にしています。

近頃はいくつかの案件で「Webサイト上に機械翻訳の機能を追加できないか」というお問い合わせをいただくことが増えてきました。
そう、Webサイトのヘッダー付近に翻訳対象の言語選択UIが鎮座し選択した言語にWebページをまるっと翻訳してくれる機能です。
有名なところはやはりGoogleウェブサイト翻訳ツールではないでしょうか。
しかし2019年に[Webサイトに追加するタイプのGoogle翻訳はサービス終了](https://translate.google.com/intl/ja/about/website/)してしまいました。
当時は無料で提供される翻訳機能の精度の高さと実装の手軽さからWebサイトに機械翻訳を実装するときに真っ先に検討したものでしたが。

![FireShot Capture 617 - ウェブサイト翻訳ツール - Google 翻訳 - https___translate.google.com_intl_ja_about_website_.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12592/79434891-6f26-65e3-0322-d95e69a5a013.png)

もちろん他社が有料提供している機械翻訳サービスも検討の候補にあがります。
しかし導入したいサービスの規模に対してコストの折り合いがつきにくく二の足を踏むことが多いです。

そこで低コストで必要なWebページに必要な言語だけを機械翻訳できる術はないか模索しました。
その中でBeta版（2019年12月19日現在）ではありますが[Firebase Extensions](https://firebase.google.com/products/extensions/)に[Translate Text](https://firebase.google.com/products/extensions/firestore-translate-text)というGoogle翻訳の拡張機能が提供されているのを見つけたので少し試してみました。


# Firebaseの設定をざっくり
細かい説明は後回しにしてFirebase Extensionsを設定して試しに動かしてみます。
Firebaseのアカウント登録などは割愛しています。

Firebaseプロジェクトの左メニューからExtensionsを選択すると拡張機能一覧が表示されます。
![FireShot Capture 618 - translate-test – 概要 – Firebase consol_ - https___console.firebase.google.co.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12592/20b0fd5e-6fc1-b8d4-05ad-0010991788b5.png)

Translate Textを選択して流れに沿って登録していきます。
![FireShot Capture 619 - translate-test – Extensions – Firebas_ - https___console.firebase.google.co.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12592/fad5d02e-0e69-c4e2-7bea-a311802bf049.png)

拡張機能を使うにはBlazeプラン必須なのでクレカ登録する必要があります。
![FireShot Capture 620 - translate-test – Extensions – Firebas_ - https___console.firebase.google.co.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12592/f4a1e9b4-a3a5-e4de-2c11-9e35544d9e0b.png)

拡張機能の構成を設定。ここで翻訳対象の言語を指定します。（あとで変更もできます）
![FireShot Capture 621 - translate-test – Extensions – Firebas_ - https___console.firebase.google.co.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12592/08272d17-b5a5-c881-916b-2c1c7b076f35.png)

拡張機能のインストールに数分かかりますが、完了後は詳細画面で翻訳対象言語の設定変更などができます。
![FireShot Capture 623 - translate-test – Extensions – Firebas_ - https___console.firebase.google.co.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12592/e23dea3f-8a7d-b437-2456-72c9bada18aa.png)

Webページの翻訳に使うのでWebアプリのFirebase登録をします。
ここでの手順は割愛。
![FireShot Capture 632 - translate-test – Extensions – Firebas_ - https___console.firebase.google.co.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12592/367c5def-6223-a3ef-a4bc-70bf0c898c64.png)


## サンプルコード
[サンプルコードはこちら](https://github.com/tabyasu/google-translate-on-firebase/blob/master/sample.html)
https://github.com/tabyasu/google-translate-on-firebase/blob/master/sample.html


このサンプルコードでは大雑把にbody以下のテキスト、HTMLタグも含めて一気にFirestoreに登録してTranslate Textの翻訳対象にしています。

```javascript
    <script>
      // FirebaseのWebアプリ設定は割愛
      var db = firebase.firestore();

      const sleep = time => {
        return new Promise((resolve, reject) => {
          setTimeout(resolve, time);
        });
      };

      const translate = async e => {
        e.preventDefault();

        // Firestoreに翻訳対象を追加登録
        const docRef = await db.collection('translations').add({
          input: document.getElementsByTagName("body")[0].innerHTML,
          created_at: firebase.firestore.FieldValue.serverTimestamp(),
        });

        // 翻訳がすぐに完了しないケースがあるので完了まで待ち
        let doc = await docRef.get();
        // TODO Error handling
        while (!doc.data().translated) {
          await sleep(10000);
          doc = await docRef.get();
        };

        const translated = doc.data().translated;
        document.getElementsByTagName("body")[0].innerHTML = translated[e.target.value];
      };

      // ページ上部にTranslate Textで設定した翻訳先の言語選択ボタンを表示
      window.onload = () => {
        const languages = { en:'英語', 'zh-CN':'中国語（簡体）', ko:'韓国語' }
        const div = document.createElement('div');
        const element = document.getElementsByTagName('body')[0];

        element.insertAdjacentHTML('afterbegin', '<div style="background-color:#EDF7FF; height:30px;"></div>');
        const translateButtons = element.firstElementChild;

        Object.keys(languages).forEach(k => {
          const button = document.createElement('button');
          button.setAttribute('type', 'button');
          button.setAttribute('name', 'translate');
          button.setAttribute('value', k);
          button.innerText = languages[k];
          button.addEventListener('click', translate);
          translateButtons.appendChild(button);
        })
      }
    </script>
  </head>
  <body>
    <div>JS導入部分のサンプルです</div>
  </body>
</html>
```


## 翻訳結果
翻訳対象のテキストデータはFirestoreに登録されます。ドキュメントIDは指定してないのでランダムなIDでドキュメント登録されていますね。
フィールドのinputに翻訳対象のテキストデータが登録され、translatedフィールドにmapで翻訳結果が登録されています。
en:英語、zh-CN:中国語（簡体）、ko:韓国語 に一気に翻訳されたようですね。
![FireShot Capture 630 - translate – Database – Firebase conso_ - https___console.firebase.google.co.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12592/277e1658-1ae8-8962-b640-aa7072869f3c.png)

日本語サンプルが下記のように翻訳されました。
![FireShot Capture 625 -  - file____Users_y-tabuchi_tabyasu_google-translate-on-firebase_sample.html.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12592/c33b17e3-f411-0392-4e6e-752eee217fa3.png)


![FireShot Capture 626 -  - file____Users_y-tabuchi_tabyasu_google-translate-on-firebase_sample.html.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12592/ab450890-c8e2-f23e-a1f1-ae5c38b87fa9.png)


![FireShot Capture 627 -  - file____Users_y-tabuchi_tabyasu_google-translate-on-firebase_sample.html.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12592/c0d20b7c-077b-b8df-3efc-11d2429b1f51.png)

![FireShot Capture 628 -  - file____Users_y-tabuchi_tabyasu_google-translate-on-firebase_sample.html.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12592/146ea775-8a8b-95c2-b2bc-de9df6fdef0c.png)


# 考察
## 料金
FirebaseのBlazeブラン（従量課金）が必須ですが、Sparkプランの無料利用分を含みます。
https://firebase.google.com/pricing

Translate TextはGCPのCloud Translation APIを利用しています。が50万文字までの翻訳は無料です。
https://cloud.google.com/translate/?hl=ja#jump-to-3
![FireShot Capture 633 - Cloud Translation  -  Google Cloud_ - https___cloud.google.com_translate_.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12592/9e13308c-19ca-fed6-0360-80560090226a.png)

## 一度に翻訳できる量
今回のサンプルや弊社コーポレートサイトのページのテキスト量だと一括で翻訳できましたが、サンプルコードのようにHTMLを文字列として一括で翻訳しようとすると翻訳結果が返ってきませんでした。
下記の注意書きがあったように長文翻訳には向かないようです。

>Cloud Translation は、短いリクエストの翻訳に最適化されています。リクエストごとに推奨される最大の長さは 2,000 コードポイントです。リクエストが非常に大きいと、利用可能な割り当ての量にかかわらず、Cloud Translation によって拒否され、400 INVALID_ARGUMENT エラーとなります。

https://cloud.google.com/translate/quotas?hl=ja

## 翻訳の精度？
僕が外国語に疎いので翻訳結果がネイティブに伝わるかどうかの精度判定はできていません。
が、Webページを一括で翻訳しようとするとHTML構文として `明日は<br>晴れるかな` のような改行を含む文章の翻訳結果が `Will it be sunny tomorrow?` のように文章にならず `tomorrow` と `Will it be sunny` に分断された翻訳結果になっていました。

# まとめ
#### コストについて
- HTMLを文字列として翻訳対象にしてしまうと当然コスト高くつくので、実戦投入する場合はテキストノードのみを翻訳対象にして丁寧に翻訳していく必要がある。
- 更新頻度の少ないページの翻訳であればFirestoreのドキュメントIDにページURLを指定して翻訳結果を登録し、ページに更新日時などの情報をもたせておいて一度翻訳した結果と差分無い場合は再利用できるようにすると翻訳コストを抑えられそう。

#### 利用方法について
- 汎用的に使えるようにFunctionsでAPI化して、ドメイン指定やAPIキーを設定するなどFirebaseのconfig情報をWebサイト上に出さないつくりにしてアクセス制御するとよさそう。
- `<br>` や `<span>` などが含まれている文章の翻訳結果を期待どおりにするためにJSの実装頑張る必要があるからちょっと大変そう。

#### 結論
UI全体を精度高く機械翻訳するには実装工数のコストがかかるが、収益の低いWebサービスに導入するには選択肢になりえる。
・・・というのが僕の感想です。

今後、テキストのみの翻訳にブラッシュアップして本番導入できることを目指したい気持ちです😃