FirebaseのCloud FunctionsからGoogle Cloud Translationを試してみる


AWS EC2インスタンスからFirebaseの環境をセットアップして、Google Cloud FunctionsからGoogle Cloud Translationにアクセスしてみます。

前回はローカルのNode.js環境からGoogle Cloud TranslationのAPIにアクセスしましたが、今回はGoogle Cloud Functionsの環境からです。

firebaseインストールと認証設定

Node.jsはインストール済みとします。

$ sudo npm install -g firebase-tools
$ firebase --version
9.8.0

インストール後、認証設定が必要です。

$ firebase login

これは対話型のコマンドです。

Google認証用のURLが表示されますので、同じようにブラウザでアクセスします。

ブラウザ上で承認すると、gcloudの認証設定と違って http://localhost:9005/?... にリダイレクトされます。firebase loginコマンドをローカルで実行している場合は、コマンドが9005番ポートを一時的に待ち受けているので、ブラウザからコマンドに承認の情報が伝わって、勝手にコマンドが完了します。

firebase loginコマンドをリモートで実行し、ブラウザだけローカルの場合には、これができません。ブラウザに表示されているURLをコピーして、リモートに別ターミナルで接続して curl "http://localhost:9005/?..." を実行します。curlコマンドは以下のようなHTMLを出力します。これで認証設定が完了です。

      <h1>Firebase CLI Login Successful</h1>
      <p>You are logged in to the Firebase Command-Line interface. You can immediately close this window and continue using the CLI.</p>

firebaseプロジェクト作成

まず空のディレクトリに移ります。

$ mkdir sample
$ cd sample

プロジェクト設定。

$ firebase init

また対話型のコマンドです。

Which Firebase CLI features do you want to set up for this folder? に対して Functions を選択。

Please select an option に対しては、Google CloudのプロジェクトですでにFirebaseを使ったことがあれば Use an existing project を選択します。Firebaseを使うのが初めてのGoogle Cloudプロジェクトであれば Create a new project を選択します。いずれもGoogle Cloudのプロジェクトはすでに存在する前提です。

あとは、 JavaScript or TypeScript の言語選択など、聞かれたことを答えます。

すると、ディレクトリの中にソースコードなどが生成されています。 .gitignore も生成されていますので、せっかくなので、

$ git init

$ git add  -A

$ git ls-files
.firebaserc
.gitignore
firebase.json
functions/.gitignore
functions/index.js
functions/package-lock.json
functions/package.json

生成されたソースコードはこの7ファイルのようです。

Cloud Translation APIを有効化

Google Cloudのコンソールにブラウザでアクセスして、使用するプロジェクトのCloud Translation APIを有効化します。

ソースコード編集

functions/index.js に以下のコードを書きました。

const functions = require("firebase-functions");
const { TranslationServiceClient } = require("@google-cloud/translate").v3;

const projectId = "xxxxxxxx";
const location = "us-central1";

// 言語判定
async function detectLanguage(text) {
    const translationClient = new TranslationServiceClient();
    const req = {
        parent: translationClient.locationPath(projectId, location),
        content: text,
        mimeType: "text/plain"
    };
    const res = await translationClient.detectLanguage(req);
    let sourceLang = null;
    for (const elem of res) {
        if (elem == null) // なぜかnullがレスポンスに含まれる
            continue;
        return elem["languages"][0]["languageCode"];
    }
}

// 翻訳
async function translate(text, sourceLang, targetLang) {
    const translationClient = new TranslationServiceClient();
    const req = {
        parent: translationClient.locationPath(projectId, location),
        contents: [text],
        mimeType: "text/plain",
        sourceLanguageCode: sourceLang,
        targetLanguageCode: targetLang,
    };
    const res = await translationClient.translateText(req);
    for (const elem of res) {
        if (elem == null) // なぜかnullがレスポンスに含まれる
            continue;
        return elem["translations"][0]["translatedText"];
    }
}

async function sample(text) {
    const result = {};
    result["original"] = text;

    // 言語判定
    const sourceLang = await detectLanguage(text);

    // 翻訳
    for (const targetLang of ["en", "ja", "zh-TW", "zh-CN", "ko"]) {
        if (targetLang == sourceLang) // Target language can't be equal to source language. というエラーを防ぐため
            continue;
        const targetText = await translate(text, sourceLang, targetLang);
        result[targetLang] = targetText;
    }

    return result;
}

exports.translation_demo = functions.https.onRequest(async (request, response) => {
  let query = request.query["q"];
  if (!query)
    query = "";
  sample(query).then(result => {
    functions.logger.info(result);
    response.send(result);
  }).catch(err => {
    functions.logger.info(err);
  });
});

functions/package.json にはGoogle Cloud Translation用のライブライを追記します。

   "dependencies": {
     "firebase-admin": "^9.2.0",
-    "firebase-functions": "^3.11.0"
+    "firebase-functions": "^3.11.0",
+    "@google-cloud/translate": "*"
   },

デプロイ

デプロイ前に npm install が必要みたいです。

$ (cd functions; npm install)

firebase deployします。これでGoogle Cloud Functionsが作成されます。JavaScriptのソースコード中に exports.translation_demo と書いていますので、 translation_demo という名前のFunctionが作られます。

$ firebase deploy

(デプロイに時間がかかるのはどうにかならないだろうか・・・)

実行

$ curl -Ssf 'https://us-central1-xxxxxxxx.cloudfunctions.net/translation_demo?q=%E5%8F%91%E7%94%9F%E5%9C%A82021%E5%B9%B43%E6%9C%8823%E6%97%A5%E5%9F%83%E5%8F%8A%E6%A0%87%E5%87%86%E6%97%B6%E9%97%B4%E4%B8%8A%E5%8D%887%E6%97%B640%E5%88%86%EF%BC%8C400%E7%B1%B3%E9%95%BF%E7%9A%84%E9%95%BF%E8%8D%A3%E6%B5%B7%E8%BF%90%E9%9B%86%E8%A3%85%E7%AE%B1%E8%88%B9%E9%95%BF%E8%B5%90%E8%BD%AE%E5%9C%A8%E5%9F%83%E5%8F%8A%E8%8B%8F%E4%BC%8A%E5%A3%AB%E8%BF%90%E6%B2%B3%E6%90%81%E6%B5%85%E3%80%82' | jq .
{
  "original": "发生在2021年3月23日埃及标准时间上午7时40分,400米长的长荣海运集装箱船长赐轮在埃及苏伊士运河搁浅。",
  "en": "At 7:40 am Egypt Standard Time on March 23, 2021, the 400-meter-long Evergreen container ship Captain Ci was stranded on the Suez Canal in Egypt.",
  "ja": "2021年3月23日のエジプト標準時午前7時40分、長さ400メートルのエバーグリーンコンテナ船のキャプテンCiがエジプトのスエズ運河で立ち往生しました。",
  "zh-TW": "發生在2021年3月23日埃及標準時間上午7時40分,400米長的長榮海運集裝箱船長賜輪在埃及蘇伊士運河擱淺。",
  "ko": "2021 년 3 월 23 일 이집트 표준시 오전 7시 40 분, 400 미터 길이의 에버그린 컨테이너 선 선장 Ci가 이집트의 수에즈 운하에 좌초했습니다."
}

(固有名詞の翻訳がうまくできてなさそうだけど、まあいいか・・・)