検索ワードを英訳する手間を減らすChrome拡張機能（自分用）を作りたい

## はじめに
新しい技術に挑戦する際に必ず立ちはだかるものといえば、そう英語ですね。
最近は翻訳サイトも充実しており、英語弱者の私でも読む分には昔ほど困ることも減りました。
ただ、英語の記事にたどり着くには英語で検索ワードを作成する必要があります。
今までは

> 検索ワードを翻訳サイトを通して翻訳する → 検索にコピーする

を延々とやってたわけですが、**ぶっちゃけめんどくさい！**
なので今回はそこを解決するために自分向けに検索ワードを翻訳してしまう自分用のChrome拡張機能を作ろうと思います。
なお、英語力を鍛えればよいという解決策は慎重な検討をもって棄却しております。

##できたもの
作成したChrome拡張機能を入れるとGoogleの検索結果画面の入力欄に以下のように翻訳アイコンが追加されます。
![image1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378060/2a4b2334-0c88-5693-87bb-34ac1463ed52.png)

追加された翻訳アイコンを押すと以下のように検索ワードが英訳されて出力されます。
![image2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378060/43b426c6-3958-8209-6c43-c9f86418f013.png)

これでいちいち検索ワードを翻訳サイトに突っ込む必要がなくなりました！

## 翻訳エンジン
###GoogleCloudTranslationAPI
翻訳エンジンにはGoogleTranslationAPIを使用しています。
50万字/月までは無料ですので、このような使い方をする範囲では無料で運用できますし　GCPに登録すれば気軽に使えますのでおすすめです。

##構成
###サーバ（Python）
渡された検索ワードをスペース区切りで分割し、TranslationAPIで検索ワードごとに翻訳して応答します。
翻訳処理は以下のように実装されています。
（ほぼCloudTranslationAPIのチュートリアルのまんまですが……）

```server.py
def translate_word(word, project_id="XXX"):

    client = translate.TranslationServiceClient()

    texts = word.split()
    location = "global"
    parent = "projects/{project_id}/locations/{location}"

    response = client.translate_text(
        request={
            "parent": parent,
            "contents": texts,
            "mime_type": "text/plain",
            "source_language_code": "ja",
            "target_language_code": "en-US"
        }
    )

    trans_words = list()

    for translation in response.translations:
        trans_words.append(translation.translated_text)

    return " ".join(trans_words)
```

また、今回は個人用なのでサーバはDockerを使用してローカルサーバとして立てています。

###クライアント（Chrome拡張）
Chrome拡張機能では以下を行います。
- Googleの検索結果に翻訳用のボタンを追加する。
- 翻訳用ボタンが押されたときに検索フォームから検索ワードを取得し、サーバから応答された値を検索フォームに反映する。

<details><summary>コード</summary><div>

```content_script.js
function onTranslate() {
    var elements = document.querySelectorAll("input[type='text']");
    
    if(elements.length > 0) {
        // 検索ボックスはinputのうち一番最初にあるものとして決め打ち
        sendTranslateAPI(elements[0].value);
    }
}

// 翻訳APIに送信する
function sendTranslateAPI(value) {
    const http = new XMLHttpRequest();
    const url = "http://localhost:9090/?word=" +encodeURIComponent(value);

    http.onload = () => {
        var elements = document.querySelectorAll("input[type='text']");
        if(elements.length > 0) {
            if(http.status === 200){
                result = JSON.parse(http.response);
                elements[0].value = result["translate"];

            }
        }
    };

    http.open("GET", url, true);
    http.send(null);
}

// Google検索結果画面にある検索フォーム内のボタンエリアを取得する
const subButtons = document.getElementsByClassName("dRYYxd")[0];
// 翻訳ボタンを作成する
const translateButton = document.createElement("button");
translateButton.type = "button";
translateButton.onclick = onTranslate;
translateButton.style.cssText = "background: none; border: none;";
const buttonImg = document.createElement("img");
buttonImg.src =  chrome.extension.getURL('img/ei-translation.svg');
buttonImg.style.cssText = "width: 30px; height: 30px";
translateButton.appendChild(buttonImg);
subButtons.appendChild(translateButton);

```

</div>

今回一番苦戦したのがGoogleの検索フォームの取得と翻訳ボタンの追加でした。
検索語句を取るフォームは必ず最初にあるだろうということで、コードのようにtextフォームの一番目を検索フォームとして決め打ちで取得しています。
※ 今回は検索語句を手修正して検索の手順を踏みたかったので、URL解析ではなくフォームから直接取得＆直接書き込みを行っています。

また、今回は個人ツールだから使えなくなっても修正可能＆スクショの見栄えのためにGoogleの検索ボックスUIに無理やり翻訳ボタンをねじ込んでいますが、Googleの更新やクラス名や構成は変わる可能性が高いため、基本的にはやらないほうが無難です。
拡張機能のアイコンに機能をもたせるか、影響の出ないような場所（body直下など)に入れておくのが良いかと思います

##おわりに
GCPのチュートリアルはかなりしっかりしているので、初めてAPIに触れてから8時間程度で拡張機能まで完成しました。~~アドベントカレンダー最終日に作業始めても間に合いますね！~~
ぶっちゃけ、かなり荒いコードですしバグ回避がしっかりしているとは言えませんが自分用のツールってそこらへんが無視できるので作ってて楽しいです。おすすめです。