AndroidでMicrosoft Translator Text APIを使って翻訳する ～実装編～

#AndroidでMicrosoft Translator Text APIを使って翻訳する ～実装編～
MicrosoftTranslatorTextAPIを使って翻訳するAndroidアプリを作ったので、
導入方法を備忘録として残しておきます。
こちらの記事では
[AndroidでMicrosoft Translator Text API を使って翻訳する ～準備編～](https://qiita.com/y_kts_n/items/5e956369d4d05bfcd6fa)
で取得したキーを使い、MicrosofTranslatorTextAPIでAndroidアプリに翻訳機能を実装していきます。

## 公式ドキュメント
http://docs.microsofttranslator.com/text-translate.html#!/default/get_Translate

## 1. APIトークンの取得
MicrosoftTranslatorTextAPIを使って翻訳するには、先にAPIトークンを取得する必要があります。
通信を行うのでAsyncTaskを継承したクラスを作成します。

```java:TranslateAPIGetTokenRequest.java

package <パッケージ名>;

import android.os.AsyncTask;
import android.util.Log;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;

import javax.net.ssl.HttpsURLConnection;

public class TranslateAPIGetTokenRequest extends AsyncTask<String, Void, String>{
    private static final String KEY = "<準備編で取得したAPIキー>"; // APIキー
    private static final String LOG_TAG = "GetTokenRequest"; // ログタグ
    private static final String HTTP_OK = "HTTP_OK"; // HttpUrlConnectionステータス
    private String mTranslateWord = ""; // 翻訳テキスト
    private String mApiToken = ""; // APIトークン
    private MainActivity mActivity; // コールバック用

    // コンストラクタ
    public TranslateAPIGetTokenRequest(MainActivity activity,String word){
        mActivity = activity;
        mTranslateWord = word;
    }

    @Override
    protected String doInBackground(String... params) {
        String urlStr = "https://api.cognitive.microsoft.com/sts/v1.0/issueToken"; //APIトークン取得リクエスト先URL
        String result = null;
        HttpURLConnection con;
        URL url = null;
        BufferedReader reader = null;
        try{
            url = new URL(urlStr);
        }catch(Exception e){
            e.printStackTrace();
        }

        try {
            con = (HttpsURLConnection) url.openConnection();
            con.setRequestMethod("POST");
            con.addRequestProperty("Content-Type","application/json");
            con.addRequestProperty("Ocp-Apim-Subscription-Key", KEY);

            final int status = con.getResponseCode();
            Log.d(LOG_TAG, "result:" + status);
            if (status == HttpURLConnection.HTTP_OK) {
                result= HTTP_OK;
                StringBuilder stringBuilder = new StringBuilder();
                reader = new BufferedReader(new InputStreamReader(con.getInputStream()));
                String inputLine;
                while ((inputLine = reader.readLine()) != null) {
                    stringBuilder.append(inputLine);
                }
                String recvData = stringBuilder.toString();
                mApiToken = recvData;
                Log.d(LOG_TAG, "http post success  recvData: " + recvData);
            } else{
                result = String.valueOf(status);
            }

        }catch(Exception e){
            Log.e(LOG_TAG,e.toString());
        }

        return result;
    }

    @Override
    protected void onPostExecute(String result) {
        Log.d(LOG_TAG, "onPostExecute, result: " + result);
        // ステータスがHTTP_OKだった場合は翻訳リクエストを開始する
        if(result.equals(HTTP_OK)) {
            TranslateAPITranslateRequest trans = new TranslateAPITranslateRequest(mActivity, mTranslateWord, mApiToken);
            trans.execute();
        }else{
            Log.d("GetTokenRequest", "onPostExecute:Error");
        }
    }
}

```

#### コード解説
##### 1. APIキーはクラス定数にしておく
    private static final String KEY = "<準備編で取得したAPIキー>"; // APIキー`

##### 2. コンストラクタでコールバック先Activityと翻訳テキストを保持する
Activityから呼び出すときに翻訳するテキストを指定したいからです。

```java
    // コンストラクタ
    public TranslateAPIGetTokenRequest(MainActivity activity,String word){
        mActivity = activity;
        mTranslateWord = word;
    }
```

##### 3. エンドポイントを指定する
APIトークン取得のエンドポイントは以下になります。
**https://api.cognitive.microsoft.com/sts/v1.0/issueToken**

```java
String urlStr = "https://api.cognitive.microsoft.com/sts/v1.0/issueToken"; //APIトークン取得リクエスト先URL
```

##### 4. リクエストパラメータを設定する
Ocp-Apim-Subscription-KeyというパラメータにAPIキーを設定する必要があります。

リクエストメソッド：POST
Content-Type：application/json
**Ocp-Apim-Subscription-Key：APIキー**(準備編で取得したキー)

```java
con.setRequestMethod("POST");
con.addRequestProperty("Content-Type","application/json");
con.addRequestProperty("Ocp-Apim-Subscription-Key", KEY);
```

##### 5. レスポンスで受けとったAPIトークンをメンバ変数で保持する
リクエストに成功すると700文字くらいの文字列が返って来ます。それがAPIトークンです。

```java
if (status == HttpURLConnection.HTTP_OK) {
                result= HTTP_OK;
                StringBuilder stringBuilder = new StringBuilder();
                reader = new BufferedReader(new InputStreamReader(con.getInputStream()));
                String inputLine;
                while ((inputLine = reader.readLine()) != null) {
                    stringBuilder.append(inputLine);
                }
                String recvData = stringBuilder.toString();
                mTranslateToken = recvData;
                Log.d(LOG_TAG, "http post success  recvData: " + recvData);
} else {
               result = String.valueOf(status);
       }
```

##### 6. 通信完了時（onPostExecuteが呼ばれたら）翻訳リクエストクラスのexecuteを実行する。
この時、コンストラクタの引数に5で取得したAPIトークン、コールバック先アクティビティ、翻訳テキストを指定します。

```java
    @Override
    protected void onPostExecute(String result) {
        Log.d(LOG_TAG, "onPostExecute, result: " + result);
        // ステータスがHTTP_OKだった場合は翻訳リクエストを開始する
        if(result.equals(HTTP_OK)) {
            TranslateAPITranslateRequest trans = new TranslateAPITranslateRequest(mActivity, mTranslateWord, mTranslateToken);
            trans.execute();
        }else{
            Log.d("GetTokenRequest", "onPostExecute:Error");
        }
    }
```
AsyncTaskクラスをこのように連続して呼び出すのはナンセンスな気がしますが…

## 2. 翻訳リクエスト
1で取得したAPIトークンを使い、翻訳リクエストを行います。
APIトークン取得リクエストと同様にAsyncTaskを継承したクラスを使います。

```java:TranslateAPITranslateRequest.java
package <パッケージ名>;

import android.os.AsyncTask;
import android.util.Log;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;

public class TranslateAPITranslateRequest extends AsyncTask<String, Void, String> {
    private static final String LOG_TAG = "TranslateRequest"; // ログタグ
    private static final String HTTP_OK = "HTTP_OK";
    private String mTranslateWord = "";// 翻訳前テキスト
    private String mTranslatedWord = ""; // 翻訳後テキスト
    private String mApiToken = ""; // APIトークン
    private MainActivity mActivity; // コールバック先Activity

    // コンストラクタ
    public TranslateAPITranslateRequest(MainActivity activity,String word,String token){
        Log.d(LOG_TAG,"word:" + word);
        mActivity = activity;
        mTranslateWord = word;
        mApiToken = token;
    }

    @Override
    protected String doInBackground(String... params) {
        Log.d(LOG_TAG,"doInBackground,translateWord:" + mTranslateWord);

        String urlStr = null;
        try {
            urlStr = "http://api.microsofttranslator.com/v2/Http.svc/Translate?from=ja&to=en&text=" + URLEncoder.encode(mTranslateWord, "UTF-8") ;
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        String result = null;
        HttpURLConnection con;
        URL url = null;
        BufferedReader reader = null;
        try{
            url = new URL(urlStr);
        }catch(Exception e){
            e.printStackTrace();
        }

        try {
            con = (HttpURLConnection) url.openConnection();
            con.setRequestMethod("GET");
            con.addRequestProperty("Authorization","Bearer " + mApiToken);

            final int status = con.getResponseCode();
            Log.d(LOG_TAG, "result:" + status);
            if (status == HttpURLConnection.HTTP_OK) {
                // レスポンスを受け取る処理等
                result=HTTP_OK;
                StringBuilder stringBuilder = new StringBuilder();
                reader = new BufferedReader(new InputStreamReader(con.getInputStream()));
                String inputLine;
                while ((inputLine = reader.readLine()) != null) {
                    stringBuilder.append(inputLine);
                }
                mTranslatedWord = stringBuilder.toString();
                mTranslatedWord = mTranslatedWord.replace("<string xmlns=\"http://schemas.microsoft.com/2003/10/Serialization/\">", "");
                mTranslatedWord = mTranslatedWord.replace("</string>", "");
                Log.d(LOG_TAG, "http post success recvData: " + mTranslatedWord);
            } else{
                result=String.valueOf(status);
            }

        }catch(Exception e){
            Log.e(LOG_TAG, e.toString());
        }

        return result;
    }

    @Override
    protected void onPostExecute(String result) {
        Log.d(LOG_TAG, "onPostExecute, result:" + result);
        if(result.equals(HTTP_OK)) {
            mActivity.callback(MainActivity.CALLBACK_FINISH_TRANSLATE_CODE, mTranslatedWord);
        }else{
            Log.d(LOG_TAG, "onPostExecute:Error");
        }
    }
}

```
#### コード解説
##### 1. コンストラクタでコールバック先Activity、翻訳前テキスト、APIトークンを保持する
```java
    // コンストラクタ
    public TranslateAPITranslateRequest(MainActivity activity,String word,String token){
        Log.d(LOG_TAG,"word:" + word);
        mActivity = activity;
        mTranslateWord = word;
        mApiToken = token;
    }
```

##### 2. エンドポイントとURLパラメータを指定する
翻訳リクエストのエンドポイントは以下になります。
**http://api.microsofttranslator.com/v2/Http.svc/Translate**
URLパラメータは以下になります。
text：翻訳したいテキスト。**必須**
to：翻訳後の言語コード。**必須**
from：翻訳前の言語コード。任意
appid：APIトークン。**必須だが、ヘッダーのパラメータ：AuthorizationでAPIトークンを指定する場合は必要ない。**

```java
    try {
           urlStr = "http://api.microsofttranslator.com/v2/Http.svc/Translate?from=ja&to=en&text=" + URLEncoder.encode(mTranslateWord, "UTF-8") ;
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
```

今回は日本語から英語への翻訳を行うので、from=ja、to=enと指定しています。
APIトークンはこのあとヘッダーパラメータで指定します。

##### 3. リクエストパラメータを指定する
リクエストメソッド：GET（POSTでも問題ない説あり）
Authorization：APIトークン（※）

※URLパラメータ：appidを指定していない場合は、
4でも触れましたが、ヘッダーパラメータのAuthorizationでAPIトークンを指定する必要があります。
逆に、URLパラメータでAPIトークンを指定する場合はAuthorizationにAPIトークンを指定する必要はありません。
どっちかに指定されてれば大丈夫です。

以下がAPIトークンの形式になります。
**"Bearer \<APIトークン\>"**
頭にBearer を付ける必要があります。**BearerとAPIトークンの間に半角スペースが入っていることに注意してください。**
URLパラメータでappidを指定する場合もこの形式になります。

```java
    con = (HttpURLConnection) url.openConnection();
    con.setRequestMethod("GET");
    con.addRequestProperty("Authorization","Bearer " + mApiToken);
```

##### 4. レスポンスで受け取った翻訳結果を不要な部分を置換し、メンバ変数に保持する
リクエストに成功すると、以下の形式で翻訳結果が返って来ます。
```
<string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">[翻訳結果]</string>
```
翻訳結果だけを返したいので要らない部分を削除します。

```java

    mTranslatedWord = stringBuilder.toString();
    mTranslatedWord = mTranslatedWord.replace("<string xmlns=\"http://schemas.microsoft.com/2003/10/Serialization/\">", "");
    mTranslatedWord = mTranslatedWord.replace("</string>", "");
```
かなり強引？

##### 5. 通信完了時（onPostExecuteが呼ばれたら）Activityにコールバックする。
通信が成功したら、受け取った翻訳結果をActivityにコールバックします。
MainActivityで実装したcallback関数を使っています。

```java

    @Override
    protected void onPostExecute(String result) {
        Log.d(LOG_TAG, "onPostExecute, result:" + result);
        if(result.equals(HTTP_OK)) {
            mActivity.callback(MainActivity.CALLBACK_FINISH_TRANSLATE_CODE, mTranslatedWord);
        }else{
            Log.d(LOG_TAG, "onPostExecute:Error");
        }
    }
```

あとはActivity側でTextViewなどに翻訳結果を表示してやればOKです。

```java:MainActivity.java
 /**
   * コールバックメソッド。
   * バックグラウンド処理終了後に呼び出される。
   */
    public void callback(final int responseCode, String word) {
        if (CALLBACK_FINISH_TRANSLATE_CODE == responseCode) {
            translateResultText.setText(word);
        }
    }
```

翻訳を開始する時はActivityに以下のように記述し、APIトークンの取得から開始します。

```java
    TranslateAPIGetTokenRequest trans = new TranslateAPIGetTokenRequest(MainActivity.this, [翻訳したいテキスト]);
    trans.execute();
```

# 3. 翻訳してみた
EditTextに入力した文字列を翻訳するようにして、実際にどの程度の精度なのか試してみました。
文章はMicrosoftTranslator APIの公式ドキュメントから引っ張ってきてます。
### 1. 日⇒英
![Screenshot_20171129-140425.png](https://qiita-image-store.s3.amazonaws.com/0/154882/b9b98a77-990f-c68b-3359-18b47ab926dd.png)

### Google翻訳
![msae2.PNG](https://qiita-image-store.s3.amazonaws.com/0/154882/105e461a-20ac-571e-b383-266f729fbf72.png)

こうしてみると翻訳語の英文は全然違いますね。

### 2. 英⇒日
![Screenshot_20171129-093344.png](https://qiita-image-store.s3.amazonaws.com/0/154882/d42d78e7-4303-6238-5a86-187709fcd470.png)

### Google翻訳
![msae.PNG](https://qiita-image-store.s3.amazonaws.com/0/154882/7efcee40-7652-0b42-4705-809028d8974f.png)

### **結論**
流石にGoogleの方が翻訳精度は高そうですね。
でも無料でここまで出来ればよろしいのではないでしょうか！

以上になります。