|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2017 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the DeepL translation engine. |
|
8 """ |
|
9 |
|
10 from __future__ import unicode_literals |
|
11 try: |
|
12 str = unicode |
|
13 except NameError: |
|
14 pass |
|
15 |
|
16 import json |
|
17 import re |
|
18 |
|
19 from PyQt5.QtCore import QUrl, QByteArray |
|
20 |
|
21 from .TranslationEngine import TranslationEngine |
|
22 |
|
23 |
|
24 class DeepLEngine(TranslationEngine): |
|
25 """ |
|
26 Class implementing the translation engine for the DeepL |
|
27 translation service. |
|
28 """ |
|
29 TranslatorUrl = "https://deepl.com/jsonrpc" |
|
30 MaxTranslationTextLen = 5000 |
|
31 |
|
32 def __init__(self, plugin, parent=None): |
|
33 """ |
|
34 Constructor |
|
35 |
|
36 @param plugin reference to the plugin object |
|
37 @type TranslatorPlugin |
|
38 @param parent reference to the parent object |
|
39 @type QObject |
|
40 """ |
|
41 super(DeepLEngine, self).__init__(plugin, parent) |
|
42 |
|
43 self.__splitPattern = re.compile(r"([^\.!\?;]+[\.!\?;]*)") |
|
44 |
|
45 def engineName(self): |
|
46 """ |
|
47 Public method to return the name of the engine. |
|
48 |
|
49 @return engine name |
|
50 @rtype str |
|
51 """ |
|
52 return "deepl" |
|
53 |
|
54 def supportedLanguages(self): |
|
55 """ |
|
56 Public method to get the supported languages. |
|
57 |
|
58 @return list of supported language codes |
|
59 @rtype list of str |
|
60 """ |
|
61 return ["de", "en", "es", "fr", "it", "nl", "pl",] |
|
62 |
|
63 def getTranslation(self, requestObject, text, originalLanguage, |
|
64 translationLanguage): |
|
65 """ |
|
66 Public method to translate the given text. |
|
67 |
|
68 @param requestObject reference to the request object |
|
69 (TranslatorRequest) |
|
70 @param text text to be translated (string) |
|
71 @param originalLanguage language code of the original (string) |
|
72 @param translationLanguage language code of the translation (string) |
|
73 @return tuple of translated text (string) and flag indicating |
|
74 success (boolean) |
|
75 """ |
|
76 if len(text) > self.MaxTranslationTextLen: |
|
77 return self.tr( |
|
78 "Text to be translated exceeds the translation limit of {0}" |
|
79 " characters.").format(self.MaxTranslationTextLen), False |
|
80 |
|
81 sentences = [s for s in self.__splitPattern.split(text) if len(s) > 0] |
|
82 if originalLanguage in self.supportedLanguages(): |
|
83 originalLanguageU = originalLanguage.upper() |
|
84 else: |
|
85 originalLanguageU = "auto" |
|
86 payload = { |
|
87 "jsonrpc": "2.0", |
|
88 "method": "LMT_handle_jobs", |
|
89 "id": 1, |
|
90 "params": { |
|
91 "jobs": [{"kind": "default", "raw_en_sentence": s} |
|
92 for s in sentences |
|
93 ], |
|
94 "lang": { |
|
95 "user_preferred_langs": [ |
|
96 originalLanguage.upper(), |
|
97 translationLanguage.upper(), |
|
98 ], |
|
99 "source_lang_user_selected": originalLanguageU, |
|
100 "target_lang": translationLanguage.upper() |
|
101 }, |
|
102 "priority": 1, |
|
103 } |
|
104 } |
|
105 request = QByteArray(json.dumps(payload).encode("utf-8")) |
|
106 response, ok = requestObject.post(QUrl(self.TranslatorUrl), request, |
|
107 "json") |
|
108 if ok: |
|
109 try: |
|
110 responseDict = json.loads(response) |
|
111 except ValueError: |
|
112 return self.tr("Invalid response received from DeepL"), False |
|
113 |
|
114 if "error" in responseDict: |
|
115 return self.tr("DeepL reported an error.\nMessage: {0}")\ |
|
116 .format(responseDict["error"]["message"]), False |
|
117 |
|
118 if "result" not in responseDict: |
|
119 return self.tr("DeepL call returned an unknown result"), False |
|
120 |
|
121 if not responseDict["result"]["source_lang"] or \ |
|
122 not responseDict["result"]["target_lang"]: |
|
123 return self.tr( |
|
124 "Unsupported language code given (source: {0}," |
|
125 " target: {1}).").format( |
|
126 originalLanguage, translationLanguage), False |
|
127 |
|
128 translations = responseDict["result"]["translations"] |
|
129 if len(translations) == 0: |
|
130 return self.tr("<p>No translation found</p>"), True |
|
131 |
|
132 # show sentence by sentence separated by a line |
|
133 translationStrings = [] |
|
134 for translation in translations: |
|
135 translationStrings.append( |
|
136 "<br/>".join( |
|
137 [s["postprocessed_sentence"] for s in sorted( |
|
138 translation["beams"], |
|
139 key=lambda b: -1 * b["score"]) |
|
140 ] |
|
141 ) |
|
142 ) |
|
143 result = "<p>" + "<hr/>".join(translationStrings) + "</p>" |
|
144 |
|
145 else: |
|
146 result = response |
|
147 return result, ok |