[Python]EelとGoogletransを使ってお手軽Google翻訳アプリ

# はじめに

Googletrans APIを使ってGoogle翻訳簡易verを作ってみました。

実行環境
* Windows10
* Python3.8
* Eel 0.14.0
* Bootstrap4
* Vue.js 2.6.12

どうやらEelはPython3.9系未対応のようです。

# こんな感じの完成物
![ぷち翻訳 2021_01_04 17_42_21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001842/f061ca7b-2318-48b2-b9bd-34e44b42f8c5.png)

# Eelの使い方
簡単にPythonでGUI作れるんですね。
[[Python] EelをつかってHTML/CSS/JavaScriptでGUIを構築][1]
[1]:https://qiita.com/inoory/items/f431c581332c8d500a3b

# Googletransでお手軽Google翻訳
とりあえず実行。
[Python – googletransを試してみました。][2]
[2]:https://dev.classmethod.jp/articles/python-py-googletrans/


が、下記エラーが出ます。
`AttributeError: 'NoneType' object has no attribute 'group'`
上手くいったりいかなかったりという謎エラーなようです。

以下を参考にさせていただいてクリア。
[【python】googletransの『AttributeError: 'NoneType' object has no attribute 'group'』対策【2020/12/02追記】][3]
[3]:https://qiita.com/_yushuu/items/83c51e29771530646659

```python:translate.py
from googletrans import Translator

class MyTranslator:
    @staticmethod
    def translate(org_text,src_lang,dest_lang):
        tr = Translator(service_urls=['translate.googleapis.com'])
        # エラーが出る時と出ない時があるので、
        # 成功するまでインスタンスを作り直してから再度実行
        i = 0
        while i < 10:
            try:
                return tr.translate(org_text, src=src_lang, dest=dest_lang).text
            except Exception as e:
                tr = Translator(service_urls=['translate.googleapis.com'])
                i+=1

```

# BootstrapできれいめGUIにする

```html:index.html
<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ぷち翻訳</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css"
        integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">
</head>

<body>
    <div class="container p-5" id="app">

        <!-- 日英or英日切り替えラジオボタン -->
        <form>
            <div class="form-group">
                <div class="custom-control custom-radio">
                    <input type="radio" name="lang_radio" class="custom-control-input" id="custom-radio-1"
                        v-model="trans_str" value="翻訳" checked>
                    <label class="custom-control-label" for="custom-radio-1">
                        日本語 → English
                    </label>
                </div>
                <div class="custom-control custom-radio">
                    <input type="radio" name="lang_radio" class="custom-control-input" id="custom-radio-2"
                        v-model="trans_str" value="Translate">
                    <label class="custom-control-label" for="custom-radio-2">
                        English → 英語
                    </label>
                </div>
            </div>
        </form>

        <!-- 翻訳ボタン -->
        <button id="translate-btn" class="btn btn-primary btn-block mb-2" type="button">
            {{trans_str}}
        </button>

        <!-- ユーザー入力 -->
        <div class="form-group mb-2">
            <textarea class="form-control" id="input" rows="3" placeholder="Please input..."></textarea>
        </div>
        
        <!-- 翻訳結果 -->
        <div class="form-group">
            <textarea class="form-control" id="output" readonly rows="3"></textarea>
        </div>

    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="vue.js"></script>
    <!-- eelを使用 -->
    <script type="text/javascript" src="/eel.js"></script>
    <script type="text/javascript" src="js/main.js"></script>
    <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>
</body>

</html>
```

# ついでにVue.jsデビュー
日英、英日のラジオボタンでの切り替えに合わせて、「翻訳ボタン」のラベルを変えます。
[Vue.jsでラジオボタンをデータバインディングする][4]
[4]:https://www.paveway.info/entry/2020/03/10/vue_vmodelradiobutton


```javascript:main.js
// 翻訳関数をpythonから呼ぶ
async function translate(src, src_lang, dest_lang) {
    let ret = await eel.translate(src, src_lang, dest_lang)();
    return ret;
}

// 翻訳ボタンクリックイベント
var translate_btn = document.getElementById('translate-btn');
var lang_radio_btn = document.getElementById( "custom-radio-1" );
translate_btn.addEventListener('click', () => {
    var src_lang = 'en'
    var dest_lang = 'ja'
    // 日英or英日 切り替え
    if ( lang_radio_btn.checked ) {
        var src_lang = 'ja'
        var dest_lang = 'en'
    } 
    let original = document.getElementById('input')
    let translated = document.getElementById('output')
    if (original.value) {
        promise = translate(original.value, src_lang, dest_lang);
        promise.then((ret) => {
            translated.value = ret
            });
        }
})

// 翻訳ボタンの文字列をVueで操作
Vue.config.productionTip = false
var app = new Vue({
    el: '#app',
    data: {
        trans_str: '翻訳'
    }
})

```

# Pythonから実行する
`@eel.expose`を付けた関数がJavascriptから呼びだされます。

```python:view.py
import eel
import desktop
import numpy as np
import pandas as pd
from translate import MyTranslator

@eel.expose
def translate(src, src_lang, dest_lang):
    return MyTranslator.translate(src, src_lang, dest_lang)

def main():
    start_dir="web"
    end_point="index.html"
    size=(600,400)
    desktop.start(start_dir, end_point, size)

if __name__ == "__main__":
    main()
```

# [Eel]起動時のオプション設定
```python:desktop.py
# -*- coding: utf-8 -*-

import eel
import sys
import socket 

# 定数
# ENTRY_POINT = 'index.html'
CHROME_ARGS = [
    '--incognit',  # シークレットモード
    '--disable-http-cache',  # キャッシュ無効
    '--disable-plugins',  # プラグイン無効
    '--disable-extensions',  # 拡張機能無効
    '--disable-dev-tools',  # デベロッパーツールを無効にする
]
ALLOW_EXTENSIONS = ['.html', '.css', '.js', '.ico']

def start(start_dir, start_page, size):  
    # 画面生成
    eel.init(start_dir, allowed_extensions=ALLOW_EXTENSIONS)
    # 未使用ポート取得
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind(('', 0))
    port = s.getsockname()[1]
    s.close()
    options = {
        'mode': "chrome",
        'close_callback': exit,
        'port': port,
        'cmdline_args': CHROME_ARGS
    }
    eel.start(start_page, options=options,
        size=size, suppress_error=True)

def exit(arg1, arg2):  # 終了時の処理
    sys.exit(0)
```