--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/UiExtensionPlugins/Translator/TranslatorEngines/DeepLEngine.py Sun Dec 10 16:23:29 2017 +0100 @@ -0,0 +1,147 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing the DeepL translation engine. +""" + +from __future__ import unicode_literals +try: + str = unicode +except NameError: + pass + +import json +import re + +from PyQt5.QtCore import QUrl, QByteArray + +from .TranslationEngine import TranslationEngine + + +class DeepLEngine(TranslationEngine): + """ + Class implementing the translation engine for the DeepL + translation service. + """ + TranslatorUrl = "https://deepl.com/jsonrpc" + MaxTranslationTextLen = 5000 + + def __init__(self, plugin, parent=None): + """ + Constructor + + @param plugin reference to the plugin object + @type TranslatorPlugin + @param parent reference to the parent object + @type QObject + """ + super(DeepLEngine, self).__init__(plugin, parent) + + self.__splitPattern = re.compile(r"([^\.!\?;]+[\.!\?;]*)") + + def engineName(self): + """ + Public method to return the name of the engine. + + @return engine name + @rtype str + """ + return "deepl" + + def supportedLanguages(self): + """ + Public method to get the supported languages. + + @return list of supported language codes + @rtype list of str + """ + return ["de", "en", "es", "fr", "it", "nl", "pl",] + + def getTranslation(self, requestObject, text, originalLanguage, + translationLanguage): + """ + Public method to translate the given text. + + @param requestObject reference to the request object + (TranslatorRequest) + @param text text to be translated (string) + @param originalLanguage language code of the original (string) + @param translationLanguage language code of the translation (string) + @return tuple of translated text (string) and flag indicating + success (boolean) + """ + if len(text) > self.MaxTranslationTextLen: + return self.tr( + "Text to be translated exceeds the translation limit of {0}" + " characters.").format(self.MaxTranslationTextLen), False + + sentences = [s for s in self.__splitPattern.split(text) if len(s) > 0] + if originalLanguage in self.supportedLanguages(): + originalLanguageU = originalLanguage.upper() + else: + originalLanguageU = "auto" + payload = { + "jsonrpc": "2.0", + "method": "LMT_handle_jobs", + "id": 1, + "params": { + "jobs": [{"kind": "default", "raw_en_sentence": s} + for s in sentences + ], + "lang": { + "user_preferred_langs": [ + originalLanguage.upper(), + translationLanguage.upper(), + ], + "source_lang_user_selected": originalLanguageU, + "target_lang": translationLanguage.upper() + }, + "priority": 1, + } + } + request = QByteArray(json.dumps(payload).encode("utf-8")) + response, ok = requestObject.post(QUrl(self.TranslatorUrl), request, + "json") + if ok: + try: + responseDict = json.loads(response) + except ValueError: + return self.tr("Invalid response received from DeepL"), False + + if "error" in responseDict: + return self.tr("DeepL reported an error.\nMessage: {0}")\ + .format(responseDict["error"]["message"]), False + + if "result" not in responseDict: + return self.tr("DeepL call returned an unknown result"), False + + if not responseDict["result"]["source_lang"] or \ + not responseDict["result"]["target_lang"]: + return self.tr( + "Unsupported language code given (source: {0}," + " target: {1}).").format( + originalLanguage, translationLanguage), False + + translations = responseDict["result"]["translations"] + if len(translations) == 0: + return self.tr("<p>No translation found</p>"), True + + # show sentence by sentence separated by a line + translationStrings = [] + for translation in translations: + translationStrings.append( + "<br/>".join( + [s["postprocessed_sentence"] for s in sorted( + translation["beams"], + key=lambda b: -1 * b["score"]) + ] + ) + ) + result = "<p>" + "<hr/>".join(translationStrings) + "</p>" + + else: + result = response + return result, ok