Feat: Replaced old translator module. The new translator can translate by using LibreTranslate or DeepL with an user provided API key

This commit is contained in:
Manuel Cortez 2024-05-15 13:56:30 -06:00
parent a1eb546f23
commit ee4f254825
No known key found for this signature in database
GPG Key ID: 9E0735CA15EFE790
9 changed files with 149 additions and 134 deletions

View File

@ -13,11 +13,13 @@ cx-Freeze==7.0.0
cx-Logging==3.2.0 cx-Logging==3.2.0
decorator==5.1.1 decorator==5.1.1
demoji==1.1.0 demoji==1.1.0
deepl==1.18.0
future==1.0.0 future==1.0.0
idna==3.7 idna==3.7
importlib-metadata==7.1.0 importlib-metadata==7.1.0
iniconfig==2.0.0 iniconfig==2.0.0
libloader @ git+https://github.com/accessibleapps/libloader@bc94811c095b2e57a036acd88660be9a33260267 libloader @ git+https://github.com/accessibleapps/libloader@bc94811c095b2e57a036acd88660be9a33260267
libretranslatepy==2.1.4
lief==0.14.1 lief==0.14.1
Markdown==3.6 Markdown==3.6
Mastodon.py==1.8.1 Mastodon.py==1.8.1

View File

@ -29,3 +29,8 @@ server = string(default="")
port = integer(default=8080) port = integer(default=8080)
user = string(default="") user = string(default="")
password = string(default="") password = string(default="")
[translator]
engine=string(default="deepl")
translator_api_url=string(default="https://translate.nvda.es")
translator_api_key=string(default="")

View File

@ -1,26 +1,21 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import widgetUtils import widgetUtils
import output import output
import config
from extra import SpellChecker from extra import SpellChecker
from extra.translator import TranslatorController
class basicMessage(object): class basicMessage(object):
def translate(self, event=None): def translate(self, event=None):
pass t = TranslatorController(self.message.text.GetValue())
# dlg = translator.gui.translateDialog() if t.response == False:
# if dlg.get_response() == widgetUtils.OK: return
# text_to_translate = self.message.text.GetValue() msg = t.translate()
# language_dict = translator.translator.available_languages() self.message.text.ChangeValue(msg)
# for k in language_dict: self.message.text.SetInsertionPoint(len(self.message.text.GetValue()))
# if language_dict[k] == dlg.dest_lang.GetStringSelection(): self.text_processor()
# dst = k self.message.text.SetFocus()
# msg = translator.translator.translate(text=text_to_translate, target=dst) output.speak(_(u"Translated"))
# self.message.text.ChangeValue(msg)
# self.message.text.SetInsertionPoint(len(self.message.text.GetValue()))
# self.text_processor()
# self.message.text.SetFocus()
# output.speak(_(u"Translated"))
# else:
# return
def text_processor(self, *args, **kwargs): def text_processor(self, *args, **kwargs):
pass pass

View File

@ -1,5 +1,2 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import absolute_import from .translator import TranslatorController
from __future__ import unicode_literals
from . import translator
from . import wx_ui as gui

View File

@ -0,0 +1 @@
# -*- coding: utf-8 -*-

View File

@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
import config
from deepl import Translator
def translate(text: str, target_language: str) -> str:
key = config.app["translator"]["translator_api_key"]
t = Translator(key)
return t.translate_text(text, target_lang=target_language).text
def languages():
key = config.app["translator"]["translator_api_key"]
t = Translator(key)
langs = t.get_target_languages()
return langs

View File

@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
""" Modified Libretranslatepy module which adds an user agent for making requests against more instances. """
import json
from typing import Any, Dict
from urllib import request, parse
from libretranslatepy import LibreTranslateAPI
class CustomLibreTranslateAPI(LibreTranslateAPI):
USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
def _create_request(self, url: str, method: str, data: Dict[str, str]) -> request.Request:
url_params = parse.urlencode(data)
req = request.Request(url, method=method, data=url_params.encode())
req.add_header("User-Agent", self.USER_AGENT)
return req
def translate(self, q: str, source: str = "en", target: str = "es", timeout: int | None = None) -> Any:
url = self.url + "translate"
params: Dict[str, str] = {"q": q, "source": source, "target": target}
if self.api_key is not None:
params["api_key"] = self.api_key
req = self._create_request(url=url, method="POST", data=params)
response = request.urlopen(req, timeout=timeout)
response_str = response.read().decode()
return json.loads(response_str)["translatedText"]
def detect(self, q: str, timeout: int | None = None) -> Any:
url = self.url + "detect"
params: Dict[str, str] = {"q": q}
if self.api_key is not None:
params["api_key"] = self.api_key
req = self._create_request(url=url, method="POST", data=params)
response = request.urlopen(req, timeout=timeout)
response_str = response.read().decode()
return json.loads(response_str)
def languages(self, timeout: int | None = None) -> Any:
url = self.url + "languages"
params: Dict[str, str] = dict()
if self.api_key is not None:
params["api_key"] = self.api_key
req = self._create_request(url=url, method="GET", data=params)
response = request.urlopen(req, timeout=timeout)
response_str = response.read().decode()
return json.loads(response_str)

View File

@ -1,116 +1,61 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import logging import logging
from googletrans import Translator, LANGUAGES import threading
import wx
import config
from pubsub import pub
from . engines import libre_translate, deep_l
from .wx_ui import translateDialog
log = logging.getLogger("extras.translator") log = logging.getLogger("extras.translator")
# create a single translator instance class TranslatorController(object):
# see https://github.com/ssut/py-googletrans/issues/234 def __init__(self, text):
t = None super(TranslatorController, self).__init__()
self.text = text
self.languages = []
self.response = False
self.dialog = translateDialog()
pub.subscribe(self.on_engine_changed, "translator.engine_changed")
if config.app["translator"]["engine"] == "libretranslate":
self.dialog.engine_select.SetSelection(0)
elif config.app["translator"]["engine"] == "deepl":
self.dialog.engine_select.SetSelection(1)
threading.Thread(target=self.load_languages).start()
if self.dialog.ShowModal() == wx.ID_OK:
self.response = True
for k in self.language_dict:
if self.language_dict[k] == self.dialog.dest_lang.GetStringSelection():
self.target_language= k
pub.unsubscribe(self.on_engine_changed, "translator.engine_changed")
def translate(text="", target="en"): def load_languages(self):
global t self.language_dict = self.get_languages()
log.debug("Received translation request for language %s, text=%s" % (target, text)) self.languages = [self.language_dict[k] for k in self.language_dict]
if t == None: self.dialog.set_languages(self.languages)
t = Translator()
vars = dict(text=text, dest=target)
return t.translate(**vars).text
supported_langs = None def on_engine_changed(self, engine):
if engine == "LibreTranslate":
config.app["translator"]["engine"] = engine.lower()
elif engine == "DeepL":
config.app["translator"]["engine"] = engine.lower()
config.app.write()
threading.Thread(target=self.load_languages).start()
languages = { def translate(self):
"af": _(u"Afrikaans"), log.debug("Received translation request for language %s, text=%s" % (self.target_language, self.text))
"sq": _(u"Albanian"), if config.app["translator"].get("engine") == "libretranslate":
"am": _(u"Amharic"), translator = libre_translate.CustomLibreTranslateAPI(config.app["translator"]["translator_api_url"])
"ar": _(u"Arabic"), vars = dict(q=self.text, target=self.target_language)
"hy": _(u"Armenian"), return translator.translate(**vars)
"az": _(u"Azerbaijani"), elif config.app["translator"]["engine"] == "deepl" and config.app["translator"]["translator_api_key"] != "":
"eu": _(u"Basque"), return deep_l.translate(text=self.text, target_language=self.target_language)
"be": _(u"Belarusian"),
"bn": _(u"Bengali"),
"bh": _(u"Bihari"),
"bg": _(u"Bulgarian"),
"my": _(u"Burmese"),
"ca": _(u"Catalan"),
"chr": _(u"Cherokee"),
"zh": _(u"Chinese"),
"zh-CN": _(u"Chinese_simplified"),
"zh-TW": _(u"Chinese_traditional"),
"hr": _(u"Croatian"),
"cs": _(u"Czech"),
"da": _(u"Danish"),
"dv": _(u"Dhivehi"),
"nl": _(u"Dutch"),
"en": _(u"English"),
"eo": _(u"Esperanto"),
"et": _(u"Estonian"),
"tl": _(u"Filipino"),
"fi": _(u"Finnish"),
"fr": _(u"French"),
"gl": _(u"Galician"),
"ka": _(u"Georgian"),
"de": _(u"German"),
"el": _(u"Greek"),
"gn": _(u"Guarani"),
"gu": _(u"Gujarati"),
"iw": _(u"Hebrew"),
"hi": _(u"Hindi"),
"hu": _(u"Hungarian"),
"is": _(u"Icelandic"),
"id": _(u"Indonesian"),
"iu": _(u"Inuktitut"),
"ga": _(u"Irish"),
"it": _(u"Italian"),
"ja": _(u"Japanese"),
"kn": _(u"Kannada"),
"kk": _(u"Kazakh"),
"km": _(u"Khmer"),
"ko": _(u"Korean"),
"ku": _(u"Kurdish"),
"ky": _(u"Kyrgyz"),
"lo": _(u"Laothian"),
"lv": _(u"Latvian"),
"lt": _(u"Lithuanian"),
"mk": _(u"Macedonian"),
"ms": _(u"Malay"),
"ml": _(u"Malayalam"),
"mt": _(u"Maltese"),
"mr": _(u"Marathi"),
"mn": _(u"Mongolian"),
"ne": _(u"Nepali"),
"no": _(u"Norwegian"),
"or": _(u"Oriya"),
"ps": _(u"Pashto"),
"fa": _(u"Persian"),
"pl": _(u"Polish"),
"pt": _(u"Portuguese"),
"pa": _(u"Punjabi"),
"ro": _(u"Romanian"),
"ru": _(u"Russian"),
"sa": _(u"Sanskrit"),
"sr": _(u"Serbian"),
"sd": _(u"Sindhi"),
"si": _(u"Sinhalese"),
"sk": _(u"Slovak"),
"sl": _(u"Slovenian"),
"es": _(u"Spanish"),
"sw": _(u"Swahili"),
"sv": _(u"Swedish"),
"tg": _(u"Tajik"),
"ta": _(u"Tamil"),
"tl": _(u"Tagalog"),
"te": _(u"Telugu"),
"th": _(u"Thai"),
"bo": _(u"Tibetan"),
"tr": _(u"Turkish"),
"uk": _(u"Ukrainian"),
"ur": _(u"Urdu"),
"uz": _(u"Uzbek"),
"ug": _(u"Uighur"),
"vi": _(u"Vietnamese"),
"cy": _(u"Welsh"),
"yi": _(u"Yiddish")
}
def available_languages(): def get_languages(self):
return dict(sorted(languages.items(), key=lambda x: x[1])) languages = {}
if config.app["translator"].get("engine") == "libretranslate":
translator = libre_translate.CustomLibreTranslateAPI(config.app["translator"]["translator_api_url"])
languages = {l.get("code"): l.get("name") for l in translator.languages()}
elif config.app["translator"]["engine"] == "deepl" and config.app["translator"]["translator_api_key"] != "":
languages = {language.code: language.name for language in deep_l.languages()}
return dict(sorted(languages.items(), key=lambda x: x[1]))

View File

@ -16,23 +16,26 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
############################################################ ############################################################
from . import translator
import wx import wx
from pubsub import pub
from wxUI.dialogs import baseDialog from wxUI.dialogs import baseDialog
class translateDialog(baseDialog.BaseWXDialog): class translateDialog(baseDialog.BaseWXDialog):
def __init__(self): def __init__(self):
languages = []
language_dict = translator.available_languages()
for k in language_dict:
languages.append(language_dict[k])
super(translateDialog, self).__init__(None, -1, title=_(u"Translate message")) super(translateDialog, self).__init__(None, -1, title=_(u"Translate message"))
self.engines = ["LibreTranslate", "DeepL"]
panel = wx.Panel(self) panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL) sizer = wx.BoxSizer(wx.VERTICAL)
staticEngine = wx.StaticText(panel, -1, _(u"Translation engine"))
self.engine_select = wx.ComboBox(panel, -1, choices=self.engines, style=wx.CB_READONLY)
self.engine_select.Bind(wx.EVT_COMBOBOX, lambda event: pub.sendMessage("translator.engine_changed", engine=self.engine_select.GetValue()))
staticDest = wx.StaticText(panel, -1, _(u"Target language")) staticDest = wx.StaticText(panel, -1, _(u"Target language"))
self.dest_lang = wx.ComboBox(panel, -1, choices=languages, style = wx.CB_READONLY) self.dest_lang = wx.ComboBox(panel, -1, style = wx.CB_READONLY)
self.dest_lang.SetFocus() self.dest_lang.SetFocus()
self.dest_lang.SetSelection(0) self.dest_lang.SetSelection(0)
engineSizer = wx.BoxSizer(wx.HORIZONTAL)
engineSizer.Add(staticEngine)
engineSizer.Add(self.engine_select)
listSizer = wx.BoxSizer(wx.HORIZONTAL) listSizer = wx.BoxSizer(wx.HORIZONTAL)
listSizer.Add(staticDest) listSizer.Add(staticDest)
listSizer.Add(self.dest_lang) listSizer.Add(self.dest_lang)
@ -40,6 +43,14 @@ class translateDialog(baseDialog.BaseWXDialog):
ok.SetDefault() ok.SetDefault()
cancel = wx.Button(panel, wx.ID_CANCEL) cancel = wx.Button(panel, wx.ID_CANCEL)
self.SetEscapeId(wx.ID_CANCEL) self.SetEscapeId(wx.ID_CANCEL)
sizer.Add(engineSizer, 0, wx.EXPAND | wx.ALL, 5)
sizer.Add(listSizer, 0, wx.EXPAND | wx.ALL, 5)
sizer.Add(ok, 0, wx.ALIGN_CENTER | wx.ALL, 5)
sizer.Add(cancel, 0, wx.ALIGN_CENTER | wx.ALL, 5)
panel.SetSizer(sizer)
def set_languages(self, languages):
wx.CallAfter(self.dest_lang.SetItems, languages)
def get(self, control): def get(self, control):
return getattr(self, control).GetSelection() return getattr(self, control).GetSelection()