15
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

PythonAdvent Calendar 2021

Day 2

DeepL API x PySimpleGUIで翻訳支援アプリを作成

Last updated at Posted at 2021-12-01

#はじめに
はじめまして。NSシステムズと申します。

今更ながらDeepLの世界一の翻訳精度かつ無料のDeepL API Freeに感動しましたので、
PythonのGUIライブラリで一番簡単なPySimpleGUIを使って翻訳支援デスクトップアプリを作成してみました。PySimpeGUIはとても簡単に使えますので、見た目に強いこだわりがなければ取り敢えず使ってみていただきたいなと思います。

また、精度は間違いなく世界一なのですが利用料金も世界一なのか?という
ところも検証しております(要するにDeepL有料版はお得なのかを検証しています)。

Qiita初投稿ですが、どなたかのお役に立てばと思います。

#アプリの概要
今回開発するアプリは、DeepLAPIFreeを使って日⇄英翻訳を実施するシンプルなアプリです(図1)。
スクリーンショット 2021-11-29 午後06.43.07 午前.png
DeepLで翻訳するだけならば、オフィシャルのデスクトップ版DeepLアプリを使えば良いという声が聞こえてきそうですがデスクトップ版アプリは1回で翻訳出来る文字数が5000字という制限があります(無料版の場合)。
ただし、DeepL APIFreeを使えば、1回当たりの翻訳文字数制限はありません。500000字/月まで無料で使えます。ハリーポッターの賢者の石を丸ごと翻訳しても約36万字なので、趣味の範囲であれば十分です。

##アプリのレイアウト説明

##アプリのディレクトリ構成
今回開発するアプリのディレクトリ構成を検討します。再利用出来るようにDeepLAPI関連のdeepl.py、GUI関連のgui.py、そしてプログラムのエントリーポイントのmain.pyと3つのファイルに分けて作成します。

DeepL_GUIApp
 ├── main.py
 ├── deepl.py
 └── gui.py

##開発に必要なパッケージ
・ PySimpleGUI

##GUIレイアウトGUI.pyの作成
PySimpleGUIを使ってGUIレイアウトを作成してきます。図2で示すように、レイアウトに配置するウィジェットは以下の4種類で合計8ヶです。

・ Text ... ①②⑥⑦
・ Multiline ... ③④
・ Button ... ⑤
・ ProgressBar ... ⑥⑦
NOTE用Pythonで作るGUIアプリ入門.png

コードは以下の通りとなります。コード内では2つの関数を宣言しています。
1つ目はmain_window()関数です。GUIウィンドウを関数化しています。
2つ目のget_usage()関数ではDeepL使用率を取得してウィンドウ内のプログレスバーを更新します。

gui.py
import PySimpleGUI as sg # PySimpleGUIをインポート
import deepl # 自作のdeepl.pyをインポート

sg.theme('Reddit')

# メインウィンドウ
def main_window():
    layout = [
        [sg.Text('原文(日、英は自動判定します)', size=(37, 2), justification='center'),   # ...①
         sg.Text('DeepL翻訳(原文⇨DeepL翻訳))', size=(37, 2), justification='center')], #  ...② 
        [sg.Multiline('', key='-original_text-', size=(35, 10)),   # ...③
         sg.Multiline('', key='-translated_text-', size=(35, 10))],  # ...④ 
        [sg.Button('DeepL翻訳', size=(10, 1), key='-run_deepl-', pad=(190, 20))],  # ...⑤
        [sg.Text('DeepL使用量', size=(74, 1), justification='center')],  # ...⑥
        [sg.Text('',size=(74, 1), key='-Char_cnt-', justification='center')],  # ...⑦ 
        [sg.ProgressBar(100, orientation='h', key='-usage_bar-', visible=True, size=(35, 20),
                        pad=(70, 10))] # ....⑧
    ]

    return sg.Window("翻訳支援アプリ", layout, finalize=True)

# DeepL使用率を取得し、Windowをアップデート
def get_usage(window):
    count_result = deepl.char_cnt()
    if count_result is None:
        sg.popup('APIキーを確認して下さい')
    else:
        count_used = count_result['character_count']
        count_limit = count_result['character_limit']
        bar_used = count_used / count_limit * 100
        output_txt = str(count_used) + ' / ' + str(count_limit) + '文字'
        window['-usage_bar-'].update(bar_used)
        window['-Char_cnt-'].update(output_txt)

各種モジュール&自作関数のimport

1行目では、PySimpleGUIをインポートしています。
2行目では、自作のdeepl.pyをインポートしています。

sg.theme(‘Reddit’)は、PySimpleGUIのテーマ’Reddit’を指定しています。
公式ページにさまざまなテーマが紹介されていますので、ご興味ありましたら参照ください。

main_window()関数について

この関数内でWindowのレイアウトを設定して、戻り値として設定したlayoutを返します。
以下の説明に加えて図2とgui.pyのコードも合わせて参照下さい。図中のウィジェットの番号とコード内の番号を一致させております。

①②:テキストウィジェットで下のMultilineウィジェットの説明をしています。
③④:Multilineウィジェットです。サイズはどちらも35字、10行で設定しています。
   左側に翻訳したい原文を右側に翻訳結果を反映させます。
   キーは、それぞれ'-original_text-'と'-translated_text-'として設定しています。
⑤:ボタンウィジェットでこちらのボタンを押すとDeepL翻訳が実行されるように
   イベントを設定します。キーは、'-run_deepl-'です。
⑥:テキストウィジェットでDeepL使用量として⑦、⑧の説明です。
⑦:テキストウィジェットでDeepLの現在の使用量をテキストで表示します。
  キーは、'-Char_cnt-'として設定しています。
⑧:プログレスバーウィジェットでDeepLの現在の使用量を%バーで表示します。
  キーは、'-usage_bar-'として設定しています。

それぞれのウィジェットのもう少し詳細な説明は、こちらで説明していますのでご興味のありましたら参照下さい。

get_usage(window)関数について

この関数には引数としてwindowを渡します。DeepL APIで使用率を取得してwindowをアップデートします(図2参照)。
関数内の1行目では、自作のdeepl.pyで宣言しているchar_cnt()を使用して辞書型変数count_resultに格納しています。変数のキーと値は以下の通りです。

  • character_count:現在のDeepL使用文字数
  • character_limit:DeepL文字数リミット(DeepLAPI Freeであれば500000字です)

if count_result is None:でDeepLAPIから値を取得出来なかった場合のメッセージをポップアップで表示させています。

値を取得出来た場合は、現在のDeepL使用文字数をcount_usege、DeepL文字数リミットをcount_limitとしてcount_resultから取り出します。
そしてDeepLの使用率(%)をbar_used、使用率の具体的な数値をoutput_txtに入れて、それぞれの値でウィンドウの各ウィジェットを更新しています。

##DeepL API関連処理のdeepl.pyの作成
deepl.pyでは以下の3つの関数を定義しています。

  • translate(text, s_lang, t_lang):DeepL翻訳を実行し、翻訳結果を返す
  • char_cnt():DeepL APIの使用量を返す
  • lang_set(text):引数textが日本語かどうかを判定し翻訳先の言語を返す

deepl.pyのコードは以下の通りです。

deepl.py
import json
from urllib.parse import urlencode
from urllib.request import Request
from urllib.request import urlopen
from urllib.error import HTTPError
import re

config = {'Auth_key': 'ご自身のAuth_Keyを貼り付けて下さい',
          'Translate_ep': 'https://api-free.deepl.com/v2/translate',
          'Usage_ep': 'https://api-free.deepl.com/v2/usage'}


def translate(text, t_lang):
    headers = {
        'Content-Type': 'application/x-www-form-urlencoded; utf-8'
    }

    params = {
        'auth_key': config['Auth_key'],
        'text': text,        
        'target_lang': t_lang
    }

    req = Request(
        config['Translate_ep'],
        method='POST',
        data=urlencode(params).encode('utf-8'),
        headers=headers
    )

    try:
        with urlopen(req) as res:
            trans_result = json.loads(res.read().decode('utf-8'))            
            return trans_result['translations'][0]['text']            
    except HTTPError as e:
        print(e)


def char_cnt():
    headers = {
        'Content-Type': 'application/x-www-form-urlencoded; utf-8'
    }

    params = {
        'auth_key': config['Auth_key'],
    }

    req = Request(
        config['Usage_ep'],
        method='POST',
        data=urlencode(params).encode('utf-8'),
        headers=headers
    )
    try:
        with urlopen(req) as res:
            cnt_result = json.loads(res.read().decode('utf-8'))
            return cnt_result

    except HTTPError as e:
        print(e)

def lang_set(text):
    # default setting
    t_lang = "JA"
    if re.search(r'[ぁ-んァ-ン]', text):
        t_lang = "EN"

    return t_lang

各種モジュール&自作関数のimport

DeepLAPIの戻り値はJSON形式となりますので、jsonをインポートします。
また、WebAPIを使用しますのでurllibパッケージから今回必要なurlencodeRequesturlopenHTTPErrorモジュールをインポートします。
最後に正規表現を使いますのでreモジュールをインポートします。

また、変数configにDeepLAPIの各種環境変数を指定します。
Auth _keyのところには、ご自身のDeepLAPI FreeのAPIキーを貼り付けて下さい。
Translate_epとUsage_epはそれぞれ翻訳する場合と使用率を確認する場合のエンドポイントになります。

translate(text, t_lang)関数について

この関数は仮引数として翻訳したい文字列text、翻訳先の言語コードt_langを取ります。
翻訳元の言語コードは、deepLが自動で判定してくれますので指定しなくても大丈夫です。
今回は、日(JA)⇄英(EN)翻訳だけとしますが他の言語も言語コードを指定すれば翻訳出来ます。

header、params、reqについては、DeepLAPI技術資料に記載がありますので
説明は公式ページを参照下さい。

with urlopen(req) as res:でJSON形式のレスポンスを受け取ります。
以下は公式ページに記載のDeepLAPIからのレスポンス例です。

{ "translations": [{ "detected_source_language":"EN", "text":"Hallo, Welt!" }] }

このレスポンスをjson形式からPythonで使いやすい辞書型にjson.loadsで変換しtrans_resultへ格納します。

戻り値は、翻訳結果だけを返したいので、trans_result['translations'][0]['text']として返します。

char_cnt()関数について

この関数は、DeepL APIの使用量を取得します。
header、params、reqについては、先ほどと同様にDeepLAPI技術資料に記載がありますので
説明は割愛しますが、translate()関数との違いは、パラメータが少ない事(翻訳しないので当然ですが)、
リクエスト先エンドポイントがUsage_epである事です。
変数cnt_resultに以下のようなjson形式のレスポンスを受け取ります。こちらも辞書型に変換しています。

{ "character_count": 180118, "character_limit": 500000 }

lang_set(text)関数について

この関数は仮引数として翻訳したい文字列textを取ります。
翻訳先の言語を入れる変数としてt_langを宣言して、デフォルトで日本語コード’JA'を入れます。
if文で正規表現を使ってtextにひらがな、もしくはカタカタが含まれているかをチェックして、含まれている場合は日本語であると判断してt_langENに書き換えます。

戻り値は、JAもしくはENの言語コードです。

エントリーポイント main.pyの作成

main.pyにアプリの一連の処理をコーディングしていきます。
アプリの流れは以下の通りです。

  1. GUIウィンドウを呼び出す
  2. DeepL使用量を取得してGUIウィンドウに反映
  3. GUIイベント処理(イベントを待ち続ける)
    • DeepL実行ボタンが押された場合の処理
    • GUIウィンドウの閉じるボタンが押された際の処理

main.pyのコードは以下の通りです。

main.py
import PySimpleGUI as sg
import gui
import deepl


def main():    
    # 最初に表示するウィンドウを指定する。
    window = gui.main_window()
    # deepLの使用量を取得し、ウィンドウに表示させる。
    gui.get_usage(window)

    while True:
        event, values = window.read()
        if event == '-run_deepl-':
            message = values['-original_text-']

            # 翻訳先の言語を設定
            lng = deepl.lang_set(message)
            # DeepL翻訳実行
            trans_result = deepl.translate(message, t_lang=lng)
            # ウィンドウに翻訳結果を反映させる
            window['-translated_text-'].print(trans_result)

            # deepLの使用量を取得し、ウィンドウに表示させる。
            gui.get_usage(window)
       
        if event == sg.WIN_CLOSED or event == "Exit":
            break

    window.close()


if __name__ == '__main__':
    main()

各種モジュール&自作関数のimport

GUIアプリですので、PySimpleGUIをインポートします。
また、先ほど作成したgui.pyとdeepl.pyもインポートします。

main()関数について

プログラムの初期設定(GUIウィンドウの呼び出し)

まずは、GUIウィンドウを呼び出します。
window = gui.main_window()

そして、deepLの現在の使用率を取得してウィンドウに反映します。
window = gui.main_window()

while True:以下は、GUIのイベントループになります。

イベント:DeepL翻訳処理

DeepL翻訳ボタンには、key = '-run_deepl-'を設定していました。
ボタンが押された際の処理をif文内にコーディングします。
if event == '-run_deepl-':

message = values['-original_text-']として、GUIウィンドウ内の左側の入力エリアから翻訳したい文字列を変数messageに取得します。

このmessageを先ほどdeepl.pyで定義したlang_set()関数に渡して翻訳先の言語コードを変数langに取得します。
lng = deepl.lang_set(message)

翻訳したい文字列messageと翻訳先の言語コードlangをdeepl翻訳の実行関数であるtranslate()に渡して、翻訳を実行して実行結果を変数trans_resultに取得します。
trans_result = deepl.translate(message, t_lang=lng)

翻訳結果をウィンドウの右側のエリアに反映させます。
window['-translated_text-'].print(trans_result)

翻訳語は、DeepL使用率を更新するため再度get_usage(window)を呼び出してウィンドウを更新します。

以上で一連のDeepL翻訳処理は完了になります。

イベント:プログラムの終了処理

if event == sg.WIN_CLOSED or event == "Exit": break

こちらは、閉じるボタンが押された場合にプログラムを終了する処理です。

DeepL API Freeの利用登録

DeepL API Freeは無料ですが、登録の際にクレジットカードが必要です。これは、フリープランの乱用を防ぐためのようです。
500000字/月を超えた場合でも自動的に課金される事はありません。

利用登録方法については、こちらに画像多めでにまとめておりますので参照下さい。

DeepL APIはコストも世界一か?

DeepLの翻訳精度は、小生知りうる限りでは現時点で一番だと思いますが、その分やはりコストも高いのかが気になります。
DeepL API Freeは500000字までは無料ですが、ビジネス利用だと全く足りません。
ビジネス用途に有料版のDeepL API Proも提供されています

こちらの料金は以下の通りです。API ProでFree版と同じ50万文字を使用すると税込で1880円/月が必要となります。
・ 100万文字あたり2500円(税別)
・ 基本料金も630円/月

有料版のメリットの一つは用語集なのですが、日本語は現時点で対応しておりませんので大きなメリットを感受できません。

これ以外のメリットは、50万文字以上でも翻訳出来ることととデータセキュリティーになります。企業などで使用するのであれば、データセキュリティーは非常に重要ですが、個人で趣味等で使うのであればFreeで十分かと思います。

他サービスとの比較

国立研究開発法人情報通信研究機構が開発した、企業向けクラウド翻訳サービスMirai Transerと料金を比べてみます。
実際にどちらも使ってみましたが、精度はどちらもかなり高く、感動してしまうレベルです。
それでも、DeepLが一歩リードしている気がします。

Mirai Transerは企業向けなので、それなりの金額となっています。国内最大規模の産業翻訳サービス企業である翻訳センターのページに料金表が掲載されていました。英語→日本語の場合、基本料金が10000円/月でさらに1ワード1円です。英単語は、1ワード平均して4.7文字くらいですのでざっくり5文字で1円とすれば、1文字当たり0.2円です。

一方でDeepL API Proは1文字当たり0.0025円で基本料金は650円/月だけです。
文字数に応じて、どれだけの差が出るかを比較してみました(下表参照)。

DeepLと未来トランサーの比較.png

MiraiTranserは企業向けという事でプロ向けの金額設定となっていますが決して高いわけではないようです。
一般社団法人日本翻訳連盟が公表している翻訳料金は1ワード28円程度ですのでMiraiTranserの料金は1/28分の一です。(もちろん機械翻訳ですので、プロの翻訳家には敵いませんので単純な料金比較はフェアではありません)

DeepL API Proはさらに安いので、DeepLAPIProのコストが圧倒的にお得である事がわかりました。

以上の比較か機械翻訳同士の比較であればらDeepLのコストが圧倒的に安いということがわかりました。

精度、コスト共に圧倒的なDeepLAPIがこれまで以上に日本で広まればDeepL社も日本向けのサービスを拡充
(用語登録機能など)してもらえると思いますので、みんなでたくさん使いましょう。

最後に

本当に今更な感じですが、私のようにDeepL翻訳を最近まで使っていない人もいるかもしれませんのでそのような方は是非とも使ってみて下さい。素直に凄いな。ここまで来たかと感動されると思います。

今回作成したアプリよりももう少し複雑なアプリ作成について↓で説明しております。
Pythonで作るGUI付き翻訳支援アプリの作成

こちらのアプリでは、原文→翻訳→逆翻訳 が出来るようになっております。逆翻訳をする事で翻訳結果が正しく翻訳されているのかを確かめる事が出来ます。特に、日本語は主語を抜いてしまう事が多いので逆翻訳結果を見返すと主語が足りないために意味が異なってしまうことなどを防ぐ事が出来ます。

DeepLAPIFreeは無料で使えます。活用の用途はたくさんあると思いますので、ご自身のアプリに組み込んでみてはいかがでしょうか?

15
19
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?