Gasを用いた翻訳プログラムの実装

### 今回のお題
今回のお題は、Gasを用いた自動翻訳です。

イメージとしては、以下の通りで、翻訳したい文章・翻訳前の言語・翻訳語の言語を入力した状態でプログラムを走らせると翻訳結果がスプレッドシートに出力されるというものです。
### 参考動画
[![Image from Gyazo](https://i.gyazo.com/cf06df41da8d4fe50d8447a414e686e4.gif)](https://gyazo.com/cf06df41da8d4fe50d8447a414e686e4)
それでは、このプログラムを実装していきます。

以下、目次です。
### 目次
- LanguageAppオブジェクトとは
- 言語の指定方法
- スプレッドシートの準備
- 実際のコード

:::note info
今回はGasやスプレッドシートに関する解説はしません。
セルの指定方法や値の取得およびセットの方法などについては必要に応じて各自でお調べください。
:::
では、順番に解説していきます。
### LanguageAppオブジェクトとは
Gasを用いた翻訳では、LanguageAppオブジェクト、及びLanguageAppオブジェクトのtranslateメソッドを使用します。
いずれもGasに標準で組み込まれており、

```javascript
let input = "翻訳前の文章"
let output = LanguageApp.translate(input, "翻訳前の言語", "翻訳語の言語")
```
とすることで指定した言語に翻訳されます。

では、次に翻訳前後の言語をどのようにして指定するかを見ていきます。
### 言語の指定方法
言語を指定する際には、Gasであらかじめ決められている言語コードを用います（以下、言語コードの一例）。

| 言語 | コード |
|:-----------|------------:|
| 日本語        | ja      |
| 英語         | en      |
| 韓国語        | ko        |
| 中国語（繁体字） | zh-TW      |
| ドイツ語      | de       |

なので、"Hello, World"をドイツ語に翻訳したければ以下のようになります。

```javascript
// "Hello, World"をドイツ語に翻訳
let input = "Hello, World"
let output = LanguageApp.translate(input, "en", "de")
```
では、翻訳の仕組みがわかったところでスプレッドシートを準備します。
### スプレッドシートの準備
スプレッドシートを以下のように編集します。
[![Image from Gyazo](https://i.gyazo.com/3c233966454f6167920e3cbdc3a9b992.png)](https://gyazo.com/3c233966454f6167920e3cbdc3a9b992)
A列とC列には翻訳前後の言語を入力し、翻訳したい文章をB列に入れます。
D列は結果を出力するためのスペースなので、空欄のままで大丈夫です。

また、A/C列は以下のようなプルダウンにしています。
[![Image from Gyazo](https://i.gyazo.com/99a2369c8c32367fa459f78f813ee9db.png)](https://gyazo.com/99a2369c8c32367fa459f78f813ee9db)

では、いよいよコードを書いていきます。
### スクリプトエディタの編集
スクリプトエディタを用意し、以下のように編集してください。

:::note warning
スクリプトエディタについてはコンテナバインド推奨です。
スタンドアローン形式でも動作しますが、後々の拡張性（シート内に用意したボタンで翻訳を実行できるようにするなど）が低下します。
:::

```javascript
function translate() {
  // プルダウンの選択肢を取得
  var inputList = ["日本語","英語","韓国語","ロシア語","スペイン語","イタリア語","中国語","フランス語","ドイツ語","ポルトガル語","アラビア語"]
  // プルダウンに対応する言語コードを用意
  var outputList = ["ja","en","ko", "ru","es","it", "zh-TW", "fr", "de", "pt","ar"]
  // 操作するシート及び範囲を指定
  var spreadSheet = SpreadsheetApp.openByUrl("https://docs.google.com/spreadsheets/d/13lkG8wrPHFbAFvvGbhn8APIy-7vnHM1YsOjAU5zPnoc/edit?usp=sharing")
  var sheet = spreadSheet.getSheetByName("シート1")
  var maxRow = sheet.getRange(sheet.getMaxRows(), 2).getNextDataCell(SpreadsheetApp.Direction.UP).getRow()
  for(let i = 2; i <= maxRow; i++){
    // A列とC列から翻訳前後の言語を拾う
    var sourceLanguage = sheet.getRange(i, 1).getValue()
    var targetLanguage = sheet.getRange(i, 3).getValue()
    // 必要な言語コードを拾うために、翻訳前後の言語がリストの何番目かを調べる
    var sourceLanguageIndex = inputList.indexOf(sourceLanguage)
    var targetLanguageIndex = inputList.indexOf(targetLanguage)
    // 翻訳したい文章を取得
    var sourceText = sheet.getRange(i, 2).getValue()
    // 翻訳した結果をセルにセット
    sheet.getRange(i, 4).setValue(LanguageApp.translate(sourceText, outputList[sourceLanguageIndex], outputList[targetLanguageIndex]))
  }
}
```

以下、個別解説です。
上記のコードのみで理解できた方は、飛ばしていただいて大丈夫です。

まず、以下のコードでプルダウンの選択肢を要素とする配列を作成しています。

```javascript
  // プルダウンの選択肢を取得
  var inputList = ["日本語","英語","韓国語","ロシア語","スペイン語","イタリア語","中国語","フランス語","ドイツ語","ポルトガル語","アラビア語"]
```
次に、全く同じ順番で言語コードの配列を作ります。

```javascript
  // プルダウンに対応する言語コードを用意
  var outputList = ["ja","en","ko", "ru","es","it", "zh-TW", "fr", "de", "pt","ar"]
```
これで、プルダウンで選択した言語が何番目かが分かれば、対応する言語コードを取得することができるようになります。

あとは翻訳前後の言語と翻訳する文章を一列ずつ拾っていけば良いだけなので、以下のようにループさせていきます。

```javascript
  var maxRow = sheet.getRange(sheet.getMaxRows(), 2).getNextDataCell(SpreadsheetApp.Direction.UP).getRow()
  for(let i = 2; i <= maxRow; i++){ ... }
```
ちなみに、繰り返し処理については、B列の中でデータが入っている最も下の行を終点にしています。
それが上記のmaxRowですね。

単にスプレッドシートの最終行ということであればgetMaxRows()メソッドで取得できるのですが、確実にタイムアウトになるのでやめました。
タイムアウトと聞いてピンと来ない方については、"gas 6分"などと検索していただければたくさん情報が出てくるかなと思います。

やや脱線しましたが、これで翻訳機能については実装完了となります。
### 終わりに
先程実装完了と申しましたが、もちろんこれは最低限の機能にすぎません。

パッと思いつくだけでも、

- 対応できる言語の数を増やしたい。
- 言語と言語コードの対応関係の定義を楽にしたい（オンラインで引っ張ってくる、スプレッドシートに保存しておく、など）。
- スクリプトエディタを開かなくても、スプレッドシート上で実行できるようにしたい。
- スマホなどの他の媒体からも利用できるようにしたい。
- 翻訳元の言語が自動で設定されるようにしたい。

などなどたくさんの改善点が思いつきます。

これらについても、順次記事にしていきたいと思います。