Azure Translator を使ってみたいあなたへ-誰でもすぐに使える翻訳機能のすすめ-

# やったこと
今回は node.js を使って、Azure アカウントと node.js の実行環境さえあればこの記事をご覧の皆さんもなるべく簡単に動作を確認できるようにしてみました。

class としてサクッとお試しいただけるものを用意しましたので、他の機能とかに組み込むとかはある程度しやすい状態じゃないかなと思います。

## こんな人向け
- 翻訳系のサービスを触ってみたい
- Azure Translator API をサクッと試す/組み込んでみたい
- どういう入力に対してどういう出力がされるのか知りたい

## こういう人向けではないよ
- 既に Translator API を使ったことがあって、更なる学習をしたい
- 翻訳機能を使った超絶素晴らしいシステムの話を聴きたい


# Azure Translator とは
Azure Translator とはザックリというと、翻訳だとか辞書検索だとかを行う Azure のサービスです。API がいくつか存在していて、チュートリアルも色々とあります。

[Translator サービスとは](https://docs.microsoft.com/ja-jp/azure/cognitive-services/translator/translator-info-overview)

## どんな API があるか
ざっと以下のようなものがあります。

|  API の種類 |  メソッド  | 概要|
| ---- | ---- | ---- |
|  Languages|  GET  |各種 API で対応する言語の取得|
|  Translate|  POST |翻訳機能|
|  Transliterate|  POST |ローマ字おこし等の他の言語での文字起こしが可能。|
|  Detect|  POST |入力言語を検出(English 等)|
|  BreakSentence|  POST |文章中の単語の切れ目検出|
|  Dictionary|  POST |入力した言葉を他の国の言葉で言い換える|
|  Dictionary Exmaple|  POST |辞書検索で使用例を見つけます。|


私はテキトーに自分で一つずつ動かしてみて確認しましたが、以下のクイックスタートが割と丁寧になぞってくれているかと思います(なぜか後で気づいた)


[node.js のクイックスタート](https://docs.microsoft.com/ja-jp/azure/cognitive-services/translator/quickstart-translator?tabs=nodejs)


# 環境的な準備
Azure アカウントや node.js の実行できる環境があればそんなには苦労しないと思います。

## Azure の準備
任意のサブスクリプションで Azure へログインして、Translator のリソースを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248829/10a28b45-2559-1c6c-b4b6-c20e8caccc41.png)

価格とかもテキトーに決めます。
リージョンは後ほど使うのでメモしておくと楽かもです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248829/a5c113ea-b83c-a369-58ff-ab96caa4a300.png)

リソースが作成されたら、該当のリソースを開き、「キーとエンドポイント」から、キーを表示させてメモします。(念のためにエンドポイントもコピーしておくとなおヨシ！)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248829/ecd52d0a-fb10-f639-3b04-faa2f54ff24b.png)

## 実行環境
node.js が実行できる環境が必要です。

# 用意したサンプルについて
個人的に用意したサンプルでは Class で実装した function の引数に http リクエストの option 内の要素を入れて実行します。以下が GitHub のリンクです。

[私お手製サンプル](https://github.com/MinoMinoMinoru/AzureTranslator-ClassSample)


## 準備
### module の install
request, dotenv の module を npm で install します。
>npm install request

### .env の編集
.env にそれぞれの情報を入力します。

- TRANSLATOR_TEXT_SUBSCRIPTION_KEY   
    - Translator のリソースのキー
    - Azure Portal の「キーとエンドポイント」にて確認したやつ。
- TRANSLATOR_TEXT_SUBSCRIPTION_REGION
    - どこに立てたリソースか(eastasia 等)
    - Azure Portal にて確認できる 
- TRANSLATOR_TEXT_ENDPOINT
    - https://api.cognitive.microsofttranslator.com/ 

ちなみに、なんか実際には使用する Translator リソースの種類ごとに認証とか変わってくるみたいです。今回は`リージョンリソース`を使用してみてるっぽい（自信はない）

- グローバルリソース
    - Ocp-Apim-Subscription-Key
- リージョンリソース
    - Ocp-Apim-Subscription-Key
    - Ocp-Apim-Subscription-Region
- cognitive service マルチサービスリソース
    - Ocp-Apim-Subscription-Key
    - Ocp-Apim-Subscription-Region

```dot

TRANSLATOR_TEXT_SUBSCRIPTION_KEY=<自分の Key>
TRANSLATOR_TEXT_SUBSCRIPTION_REGION=eastasia
TRANSLATOR_TEXT_ENDPOINT=https://api.cognitive.microsofttranslator.com/
```


こんな感じでリクエストのヘッダーを用意してます。(subscriotionKey,region は環境変数で設定)

```json
"headers": {
        "Ocp-Apim-Subscription-Key": subscriptionKey,
        "Ocp-Apim-Subscription-Region": region,
        "Content-type": 'application/json',
        "X-ClientTraceId": uuidv4().toString()
}
```

これに加えて、どのリージョンでも使えるアクセストークンの方法もある
秘密鍵をトークンと交換するらしい。余計な処理増やすのも面倒だし、とりあえず今はスルーします。

気になる方は↓をご確認いただけたらと。

[Translator v3.0](https://docs.microsoft.com/ja-jp/azure/cognitive-services/translator/reference/v3-0-reference)

## サンプルの使い方

実際に使用するエンドポイント等はそれぞれの API で記載しますが、こちらのサンプルをお試しいただく際には以下を確認いただくくらいで大丈夫です。

|  要素 |   概要|
| ---- | ---- | 
|  function | 各 API 毎に Class 内にて実装している。  |
|  qs|  function 内でリクエストを送る際に使用する。変更先の言語の指定なんかもここで指定する。 |
| body | API で処理する内容。配列になっていることが多い。 |

Class 内で以下のように指定してます。エンドポイントとかは API 毎に決まっているのでもう function  内でハードコードしちゃってます。

```javascript
async sendRequest(options){
    return new Promise(function (resolve, reject) {
        request(options, function (err, res, body) {
            if (err) {
                console.log('Error: ' + err.message);
                reject("request error")
            } else {
                // console.log(body);
                resolve(JSON.parse(JSON.stringify(body)))
            }
        });
    });
}

// translate の API を呼んでいる Class 内 function
async translatorAPI(_qs, _body) {
    options.qs = _qs;options.body = _body;
    options.baseUrl = process.env[endpoint_var] + 'translate?api-version=3.0&to=es';
    return await this.sendRequest(options);
}
```

test.js 内では下記のように呼び出しています。

```javascript
async function translateTest() {
    trans = new Translator()
    const qs = {
        'api-version': '3.0',
        'to': ['de', 'ja', 'en']
    }

    const body = [{
        'text': 'おはようございます'
    }]

    var result = await trans.translatorAPI(qs, body)
    console.dir(result[0]);
}

translateTest()
```

上記の感じで、実際に API を使用する際には呼び出す function,qs,body の内容を指定するように実装してます。
# 各種 API の説明 & 動作
前置きが長かったですが、実際に作ってみた Class を使って各種 API を動かしてみます。
なお、Language API に関しては、GET リクエストを試すだけなので実装していないです。(実際の入力と出力を見てもらったら実装することはないと思われるかと)
## Languages
[Language](https://docs.microsoft.com/ja-jp/azure/cognitive-services/translator/reference/v3-0-languages) は GET リクエストを送り、Translator でサポートされている言語(日本語や英語) を取得します。

特にクエリとかもなく以下の URL へ GET リクエストを送信するだけです。
なので、ブラウザで以下の URL を入力するだけでも確認できます。(とても見にくいですが)

``` http
https://api.cognitive.microsofttranslator.com/languages?api-version=3.0
```
実行結果の一部が以下です。

```json
{
  "translation": {
    "af": {
      "name": "Afrikaans",
      "nativeName": "Afrikaans",
      "dir": "ltr"
    },
    "ar": {
      "name": "Arabic",
      "nativeName": "العربية",
      "dir": "rtl"
    },

```

これは Translation の API ではこのような言語に対応しているということを示します。(実際には 400 行以上あるのでかっと)

## Translate
基本的な翻訳機能。qs 内の to が翻訳先の言語です。


入力

``` JavaScript
const qs = {
    'api-version': '3.0',
    'to': ['de', 'ja', 'en']
}

const body = [{
    'text': 'おはようございます'
}]
        
```

出力 : 入力した言語をドイツ語、日本語(そのまま)、英語で翻訳してます。

```json
[
    {
        "detectedLanguage": {
            "language": "ja",
            "score": 1
        },
        "translations": [
            {
                "text": "Guten Morgen",
                "to": "de"
            },
            {
                "text": "おはようございます",
                "to": "ja"
            },
            {
                "text": "Good morning",
                "to": "en"
            }
        ]
    }
]
```

## Transliterate
ローマ字おこしのように、他の言語での発音を文字起こしが可能。
fromScript の言語で入力した文字を、toScript の言語で文字での発音に変換します。これらの指定(fromScript,toScript) は後述の language に依存します。

入力

```JavaScript
const qs = {
    'api-version': '3.0',
    'language': 'ja',
    "fromScript":"jpan",
    "toScript":"Latn"
}

const body = [
    {'text': 'おはようございます'},
    {'text': 'こんにちは'}
]

            
```

出力 : それぞれ入力した言葉がローマ字(Latn) に変換されました。

```json
[
  { "text": "ohayougozaimasu", "script": "Latn" },
  { "text": "Kon'nichiwa", "script": "Latn" }
]
```

なお、入力に使用する language は前述の Language API から"transliterate" の要素から以下のように確認できます。

```json
"ja": {
    "name": "Japanese",
    "nativeName": "日本語",
    "scripts": [
        {
            "code": "Jpan",
            "name": "Japanese",
            "nativeName": "日本語",
            "dir": "ltr",
            "toScripts": [
                {
                    "code": "Latn",
                    "name": "Latin",
                    "nativeName": "ラテン語",
                    "dir": "ltr"
                }
            ]
        },
        {
            "code": "Latn",
            "name": "Latin",
            "nativeName": "ラテン語",
            "dir": "ltr",
            "toScripts": [
                {
                    "code": "Jpan",
                    "name": "Japanese",
                    "nativeName": "日本語",
                    "dir": "ltr"
                }
            ]
        }
    ]
}
```

## Detect
テキストの言語が日本語などの何語に相当するかを検出します。

入力

```JavaScript
const qs = {
    'api-version': '3.0'
}

const body = [
    {'text': 'おはようございます'},
    {'text': 'Hello'}
]
            
```

出力 : 上記の body 内容をそれぞれ日本語、英語と検出できてます。

```json
[
  {
    "language": "ja",
    "score": 1,
    "isTranslationSupported": true,
    "isTransliterationSupported": true
  },
  {
    "language": "en",
    "score": 1,
    "isTranslationSupported": true,
    "isTransliterationSupported": false
  }
]
```


## BreakSentence
文の切れ目の位置を検出します。

入力

```JavaScript
const qs = {
    'api-version': '3.0'
}

const body = [    { "Text": "How are you? I am fine. What did you do today?" }]       
```

出力 : 言語を検出してから文の長さを確認します。配列は各文の長さを示します。ピリオドや ? マーク等も文字数に含めるのと、文の合間の半角スペースは前の文章の文字数に含まれます。

例) 配列の一つ目の要素は "How are you? " と半角スペースまで含めて 13 文字のカウント。

```json
[
  {
    "detectedLanguage": { "language": 'en', "score": 1 },
    "sentLen": [ 13, 11, 22 ]
  }
]
```


## Dictionary
辞書検索をします。該当する入力した言語から対応する任意の言語に変換するとどのような言葉になるかを出力します。

入力 : from の言語と同様の意味の単語を to の言語で探します。

```JavaScript
const qs = {
    'api-version': '3.0',
    'from': 'en',
    'to': 'ja'
}
const body = [{ "Text": "Hello" }]
```

出力 : Hello という言葉を色んな日本語に変換してます。

```json
{
  "normalizedSource": 'hello',
  "displaySource": 'Hello',
  "translations": [
    {
      "normalizedTarget": 'こんにちは',
      "displayTarget": 'こんにちは',
      "posTag": 'NOUN',
      "confidence": 0.401,
      "prefixWord": '',
      "backTranslations": [Array]
    },
    {
      "normalizedTarget": 'こんにち',
      "displayTarget": 'こんにち',
      "posTag": 'NOUN',
      "confidence": 0.2792,
      "prefixWord": '',
      "backTranslations": [Array]
    },
    {
      "normalizedTarget": 'ハロー',
      "displayTarget": 'ハロー',
      "posTag": 'NOUN',
      "confidence": 0.2118,
      "prefixWord": '',
      "backTranslations": [Array]
    },
    {
      "normalizedTarget": 'もしもし',
      "displayTarget": 'もしもし',
      "posTag": 'NOUN',
      "confidence": 0.1081,
      "prefixWord": '',
      "backTranslations": [Array]
    }
  ]
}
```




## Dictionary-example
正直ちょっとこの API に関しては他に比べてよく理解できないところが多いのですが、辞書検索で使用例を見つけます。各言語のものを入力する必要があるとかもよく理解はできてないのですが、例文をいくつか検索してくれます。

入力

```JavaScript
const qs = {
    'api-version': '3.0',
    'from': 'en',
    'to': 'ja'
}

const body =[{
    'Text': 'fly', "Translation":"飛ぶ"
}]
```

出力

```json
{
  "normalizedSource": 'fly',
  "normalizedTarget": '飛ぶ',
  "examples": [
    {
      "sourcePrefix": 'This species ',
      "sourceTerm": 'fly',
      "sourceSuffix": ' high with a strong flight.',
      "argetPrefix": 'この種は強いフライトを高く',
      "targetTerm": '飛ぶ',
      "targetSuffix": '。'
    },
    {
      "sourcePrefix": 'But they cannot ',
      "sourceTerm": 'fly',
      "sourceSuffix": '.',
      "targetPrefix": 'しかし、',
      "targetTerm": '飛ぶ',
      "targetSuffix": 'ことはできません。'
    },
    {
      "sourcePrefix": 'His thought was, to ',
      "sourceTerm": 'fly',
      "sourceSuffix": '.',
      "targetPrefix": '彼の思想は',
      "targetTerm": '飛ぶ',
      "targetSuffix": 'ことだった。'
    }
}
```


基本的な API の使用方法は以上です。

# まとめ
一般的な REST の Web API 形式なので使いやすいですね。本当はモデルを自分で作ったりとかもできるみたいなので、興味がある方は挑戦してみても良いかもです。(今回は間に合わなかったので見なかったことにしました！)

[Custom Translator とは](https://docs.microsoft.com/ja-jp/azure/cognitive-services/translator/custom-translator/overview)

また、最後におまけとして Bot アプリに翻訳機能を組み込んでみたのですが、まあ Class 呼び出してちょっとそれっぽく Body やら qs やらに値を入れ込むだけなので、自分のシステムへの組み込みなんかも楽にできそうな印象を受けました。

# おまけ : シンプルな翻訳 Bot を作ってみた。
折角 Class の形式にしたので、他のシステムに組み込んでみました。
今回は私が使い慣れている Bot Framework SDK を用いた Bot プログラムで翻訳 Bot を作ります。ちょっと試してみるだけなので、一旦はシンプルな翻訳機能のみを実装してみます。

## 準備
以下の GitHub から 02-echo-bot というサンプルを落としてきます。

https://github.com/microsoft/BotBuilder-Samples/tree/main/samples/javascript_nodejs/02.echo-bot

詳細の説明は省きますが、元の動作について簡単に言うと、bot.js の中の onMessage() にて以下のように記述されていて、オウム返しをする動作をする Bot です。

```bot.js
this.onMessage(async (context, next) => {
      const replyText = `Echo: ${ context.activity.text }`;
      await context.sendActivity(MessageFactory.text(replyText, replyText));
      // By calling next() you ensure that the next BotHandler is run.
      await next();
});
```

エミュレータの結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248829/4c3473bc-f340-dbf7-a518-8b538eae44cb.png)

このプロジェクトをちょっといじってみるくらいで先ほどの翻訳機能が試せます。
- TranslatorClass.js (今回作ってみたクラス) を追加
- .env にTranslator の Key 等を追加
- npm install を適宜実行
- bot.js に以下の記述を追加

```bot.js
// import とインスタンスの作成
const {Translator}  = require('./TranslatorClass');
trans = new Translator()

// 中略

// constractor 内に qs を追加
const qs = {
            'api-version': '3.0',
            'to': ['de', 'ja', 'en']
}
this.onMessage(async (context, next) => {
    const body = [{
        'text': context.activity.text
    }]
    var result = await trans.translatorAPI(qs, body)
    for(var i=0;i<result[0].translations.length;i++) await context.sendActivity(MessageFactory.text(JSON.stringify(result[0].translations[i].text)));
    //By calling next() you ensure that the next BotHandler is run.
    await next();
});
```
実行結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248829/ee6d9f71-818d-f7d3-4e94-3d0c38095aac.png)

Bot のサンプルを落としてから翻訳機能追加するまでで数分で出来ました。
ぜひこんな感じで他のプログラムに組み込んでみてください。

なお、もし Azure Bot Service や BotFramework SDK に興味がある場合は以下をご参考ください。

- [初めての Azure Bot Service - Sample code と local test 編 -](https://qiita.com/MinoMinoMinoru/items/d02bcea1a7b681c40240 )
- [初めての Azure Bot Service -multi-turn-prompt と Azure への Deploy -](https://qiita.com/MinoMinoMinoru/items/410ea775baa9943f59ce)

また、今回使用した翻訳する簡単な Bot の変更後のコードは以下の通りです。

<details><summary>bot.js の全体</summary><div>

```js
const { ActivityHandler, MessageFactory } = require('botbuilder');
const {Translator}  = require('./TranslatorClass');

trans = new Translator()
class EchoBot extends ActivityHandler {
    constructor() {
        const qs = {
            'api-version': '3.0',
            'to': ['de', 'ja', 'en']
        }
    
        super();
        // See https://aka.ms/about-bot-activity-message to learn more about the message and other activity types.
        this.onMessage(async (context, next) => {
            const body = [{
                'text': context.activity.text
            }]
            var result = await trans.translatorAPI(qs, body)
            for(var i=0;i<result[0].translations.length;i++) await context.sendActivity(MessageFactory.text(JSON.stringify(result[0].translations[i].text)));
            // By calling next() you ensure that the next BotHandler is run.
            await next();
        });

        this.onMembersAdded(async (context, next) => {
            const membersAdded = context.activity.membersAdded;
            const welcomeText = 'Hello and welcome!';
            for (let cnt = 0; cnt < membersAdded.length; ++cnt) {
                if (membersAdded[cnt].id !== context.activity.recipient.id) {
                    await context.sendActivity(MessageFactory.text(welcomeText, welcomeText));
                }
            }
            // By calling next() you ensure that the next BotHandler is run.
            await next();
        });
    }
}

module.exports.EchoBot = EchoBot;
```
</div></details>