GASとVueを使ってGoogle再翻訳が手軽にできるクソWebサービスをつくってみた

8 min read読了の目安(約7400字

https://twitter.com/haitekukaito/status/1376889450521784327

Google再翻訳(Google逆翻訳)がさくっとできるやつを作りました。

https://reverslation.uko.jp

個人的な事情でメンタルが死にかけていたのですがツイッターでたまたま流れてきたRTを見て少しだけ元気が出ました。で、気がついたらクソコードを生産してしまっていたので供養です。
Googleから怒られたら消します

材料

必要なもの:Googleアカウント・静的Webサーバー(NetlifyとかGitHubPagesとか)
主な言語:Javascript(Vue.js)・GAS
所要時間:約4時間(コード基礎部分+デプロイ+記事執筆 / アレンジ含まず)

再翻訳APIをGASでつくる

Google翻訳のAPIをそのまま使うと間違いなく課金課金課金課金課金課金になるので、こちらの神がかった方法をお借りしました。

https://qiita.com/satto_sann/items/be4177360a0bc3691fdf

GASコード

ちょっと古い記事ですが、問題なく翻訳APIが使えます。
アプリケーションから使うので、Postで取れる形式にしてみました。

main.gs
function doPost(e) {
    // リクエストパラメータを取得する
    const p = e.parameter;

    // 逆変換回数(最大5回まで・そんなにいらんかも)
    let n = (p.n) ? parseInt(p.n) : 1;
    if (n < 1) n = 1;
    if (n > 5) n = 5;
    
    //  LanguageAppクラスを用いて翻訳を実行
    let translatedText;
    let revertedText = p.text;
    for (let i = 0; i < n; i++) {
      translatedText = LanguageApp.translate(revertedText, "ja", "en");
      revertedText = LanguageApp.translate(translatedText, "en", "ja");
    }

    // レスポンスボディの作成
    let body;
    if (translatedText && revertedText) {
        body = {
          code: 200,
          text: revertedText
        };
    } else {
        body = {
          code: 400,
          text: "Bad Request"
        };
    }

    // レスポンスの作成
    const response = ContentService.createTextOutput();
    // Mime TypeをJSONに設定
    response.setMimeType(ContentService.MimeType.JSON);
    // JSONテキストをセットする
    response.setContent(JSON.stringify(body));

    return response;
}

Postmanで確認してみました。夏目マッドキャップボーイ。

デプロイ

参考記事の画像と異なり、GASエディタが新しくなっていたため以下の点に注意です。

デプロイは右上のところから。
スクリプトを更新すると「新しいデプロイ」となり、エンドポイントURLが変わってしまうことに気をつけましょう。

ウェブアプリとして。

一番下の選択肢「アクセスできるユーザー」を「自分」にします。
バグだと思うんですが、説明文を入力後に「自分」を選択するとなぜか選択欄が空白になってしまいますが、問題なくデプロイはできます。
デプロイ後、「ウェブアプリのURL」をコピーしてどこかに控えておきます。

アプリのメインページをつくる

先ほどのGASで作ったAPIを扱うメインのHTMLを組みます。
今回犠牲になるHTMLテンプレートはこちら。

https://templatemag.com/minimal-bootstrap-template/

フリーライセンスでZIPダウンロードして展開し、index.htmlのみ編集します。
決まりにより、クレジット表記は外さないようにします。
AJAXでコメントフォームを使うのも(フリーライセンスでは)禁止ですが、ただ単に自分のAPIを叩くだけなのでライセンス的にはセーフだと思います。やばかったら教えてください。(テンプレ替えるかお金払わなきゃいけない)

以下のように書き換えます。
他のファイルは一切触らなくてOKです。

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>Google再翻訳</title>
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <link href="https://fonts.googleapis.com/css?family=Oswald:400,300,700|EB+Garamond" rel="stylesheet">
  <link href="https://fonts.googleapis.com/css?family=Sawarabi+Mincho" rel="stylesheet">
  <link href="https://fonts.googleapis.com/css?family=M+PLUS+Rounded+1c" rel="stylesheet">
  <link href="lib/bootstrap/css/bootstrap.min.css" rel="stylesheet">
  <link href="css/style.css" rel="stylesheet">
</head>

<body>
<div class="container-fluid" style="padding: 0;" id="app">

  <!-- ========== 文字ドーンと出るとこ ========== -->
  <div id="headerwrap">
    <div class="container">
      <div class="logo">
        <img src="img/logo.png">
      </div>
      <br>
      <div class="row">
        <h1 style="font-family: 'Sawarabi Mincho';">{{ resultText }}</h1><br>
        <h3 style="font-family: 'M PLUS Rounded 1c';">{{ targetText }}<i v-if="targetText"> - を<span v-if="n > 1">{{ n }}回</span>再翻訳</i></h3><br><br>
        <div class="col-lg-6 col-lg-offset-3"></div>
      </div>
    </div>
  </div>

  <!-- ========== 変換フォームいれるとこ ========== -->
  <div id="f">
    <div class="container">
      <div class="row">
        <h3>REVERSLATION</h3>
        
        <div class="col-lg-6 col-lg-offset-3" style="margin-top: 50px; margin-bottom: 50px;">
          <label for="targetInput" class="form-label">再翻訳したい言葉を入力</label>
          <input type="text" class="form-control" id="targetInput" v-model="targetText">
          <div class="form-inline" style="margin-top: 5px;">
            <div class="form-group">
              <label for="range">回数</label>
            </div>
            <div class="form-group" style="margin: 0 10px;">
              <select class="form-control form-control-sm" v-model="n">
                <option value="1">1回</option>
                <option value="2">2回</option>
                <option value="3">3回</option>
                <option value="4">4回</option>
                <option value="5">5回</option>
              </select>
            </div>
            <div class="form-group">
              <button type="submit" class="btn btn-primary" v-on:click="reverslation">{{ buttonText }}</button>
            </div>
          </div>
        </div>

      </div>
    </div>
  </div>

  <div id="copyrights">
    <div class="container">
      <div class="credits">
        <!--
          You are NOT allowed to delete the credit link to TemplateMag with free version.
          You can delete the credit link only if you bought the pro version.
          Buy the pro version with working PHP/AJAX contact form: https://templatemag.com/minimal-bootstrap-template/
          Licensing information: https://templatemag.com/license/
        -->
        Created with Minimal template by <a href="https://templatemag.com/">TemplateMag</a>
      </div>
    </div>
  </div>

  <!-- JavaScript Libraries -->
  <script src="lib/jquery/jquery.min.js"></script>
  <script src="lib/bootstrap/js/bootstrap.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/axios@0.21.1/dist/axios.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.min.js"></script>

  <script>
    new Vue({
      el: '#app',
      data() {
        return {
          buttonText: '再翻訳',
          targetText: '',
          resultText: 'Google Reverslation',
          n: 1,
        };
      },
      methods: {
        async reverslation() {
          try {
	    // UI表示変更
            this.buttonText = '待ってね〜';
            this.resultText = '再翻訳中...';
	    // POSTのフォームデータを作成(翻訳したい本文と回数)
            const data = new FormData();
            data.append('text', this.targetText);
            data.append('n', this.n);
	    // 非同期でGASの再翻訳APIに投げる
            const res = await axios.post('GASのウェブアプリURLをここに', data);
	    // 返却値を見て正常に処理されたか判定
            if (res.data.code === 200 && res.data.text) {
              this.resultText = res.data.text; // 結果はここにはいる
              this.buttonText = 'もう一度再翻訳する'; // ボタン表示
            } else {
              this.resultText = '失敗しました';
            }
          } catch (e) {
            // axiosのエラー
          }
        },
      },
    })
  </script>

</div>
</body>
</html>

axiosを使ってGASにPOSTアクセスする

たぶん一番重要なところはここだけです。
日本語とか入るしエンコードもややこしいのでもうそのままFormDataでPOSTが一番楽です。

// POSTのフォームデータを作成(翻訳したい本文と回数)
const data = new FormData();
data.append('text', this.targetText);
data.append('n', this.n);
// 非同期でGASの再翻訳APIに投げる
const res = await axios.post('GASのウェブアプリURLをここに', data);

結果

フォントには「さわらび明朝」を使いました。
言葉によってフォントテイストを変えられれば楽しそうですよね。

https://fonts.google.com/specimen/Sawarabi+Mincho

考察

最近のGoogle翻訳は精度が高いので、ひと昔まえほどは面白くならないみたいです。
中間の言語種別を変えればまた結果も変わってきそうです。

https://cloud.google.com/translate/docs/languages