|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2018 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the IBM Watson 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 |
|
18 from PyQt5.QtCore import QUrl, QByteArray |
|
19 |
|
20 from E5Gui import E5MessageBox |
|
21 |
|
22 from .TranslationEngine import TranslationEngine |
|
23 |
|
24 |
|
25 class IbmWatsonEngine(TranslationEngine): |
|
26 """ |
|
27 Class implementing the translation engine for the IBM Watson Language |
|
28 Translator service. |
|
29 """ |
|
30 # Documentation: |
|
31 # https://www.ibm.com/watson/developercloud/language-translator |
|
32 # |
|
33 # Start page: |
|
34 # https://www.ibm.com/watson/services/language-translator/ |
|
35 |
|
36 def __init__(self, plugin, parent=None): |
|
37 """ |
|
38 Constructor |
|
39 |
|
40 @param plugin reference to the plugin object |
|
41 @type TranslatorPlugin |
|
42 @param parent reference to the parent object |
|
43 @type QObject |
|
44 """ |
|
45 super(IbmWatsonEngine, self).__init__(plugin, parent) |
|
46 |
|
47 self.__ui = parent |
|
48 |
|
49 self.__availableTranslations = {} |
|
50 # dictionary of sets of available translations |
|
51 |
|
52 self.__getTranslationModels() |
|
53 |
|
54 def engineName(self): |
|
55 """ |
|
56 Public method to return the name of the engine. |
|
57 |
|
58 @return engine name |
|
59 @rtype str |
|
60 """ |
|
61 return "ibm_watson" |
|
62 |
|
63 def supportedLanguages(self): |
|
64 """ |
|
65 Public method to get the supported languages. |
|
66 |
|
67 @return list of supported language codes |
|
68 @rtype list of str |
|
69 """ |
|
70 return list(self.__availableTranslations.keys()) |
|
71 |
|
72 def supportedTargetLanguages(self, original): |
|
73 """ |
|
74 Public method to get a list of supported target languages for an |
|
75 original language. |
|
76 |
|
77 @param original original language |
|
78 @type str |
|
79 @return list of supported target languages for the given original |
|
80 @rtype list of str |
|
81 """ |
|
82 targets = self.__availableTranslations.get(original, set()) |
|
83 return list(targets) |
|
84 |
|
85 def hasTTS(self): |
|
86 """ |
|
87 Public method indicating the Text-to-Speech capability. |
|
88 |
|
89 @return flag indicating the Text-to-Speech capability |
|
90 @rtype bool |
|
91 """ |
|
92 return False |
|
93 |
|
94 def getTranslation(self, requestObject, text, originalLanguage, |
|
95 translationLanguage): |
|
96 """ |
|
97 Public method to translate the given text. |
|
98 |
|
99 @param requestObject reference to the request object |
|
100 @type TranslatorRequest |
|
101 @param text text to be translated |
|
102 @type str |
|
103 @param originalLanguage language code of the original |
|
104 @type str |
|
105 @param translationLanguage language code of the translation |
|
106 @type str |
|
107 @return tuple of translated text and flag indicating success |
|
108 @rtype tuple of (str, bool) |
|
109 """ |
|
110 apiKey = self.plugin.getPreferences("IbmKey") |
|
111 if not apiKey: |
|
112 return self.tr("A valid IBM Watson Language Translator key is" |
|
113 " required."), False |
|
114 translatorUrl = self.plugin.getPreferences("IbmUrl") |
|
115 if not translatorUrl: |
|
116 return self.tr("A valid IBM Watson Language Translator URL is" |
|
117 " required."), False |
|
118 |
|
119 params = "?version=2018-05-01" |
|
120 url = QUrl(translatorUrl + "/v3/translate" + params) |
|
121 |
|
122 requestDict = { |
|
123 "text": [text], |
|
124 "source": originalLanguage, |
|
125 "target": translationLanguage, |
|
126 } |
|
127 request = QByteArray(json.dumps(requestDict).encode("utf-8")) |
|
128 |
|
129 extraHeaders = [ |
|
130 (b"Authorization", |
|
131 b"Basic " + QByteArray( |
|
132 b"apikey:" + apiKey.encode("utf-8")).toBase64()) |
|
133 ] |
|
134 |
|
135 response, ok = requestObject.post(url, request, dataType="json", |
|
136 extraHeaders=extraHeaders) |
|
137 if ok: |
|
138 try: |
|
139 responseDict = json.loads(response) |
|
140 except ValueError: |
|
141 return self.tr("Invalid response received"), False |
|
142 |
|
143 if "translations" not in responseDict: |
|
144 return self.tr("No translation available."), False |
|
145 |
|
146 result = "" |
|
147 translations = responseDict["translations"] |
|
148 for translation in translations: |
|
149 result += translation["translation"] |
|
150 if translation != translations[-1]: |
|
151 result += "<br/>" |
|
152 else: |
|
153 result = response |
|
154 return result, ok |
|
155 |
|
156 def __adjustLanguageCode(self, code): |
|
157 """ |
|
158 Private method to adjust a given language code. |
|
159 |
|
160 @param code code to be adjusted |
|
161 @type str |
|
162 @return adjusted language code |
|
163 @rtype str |
|
164 """ |
|
165 if code == "zh": |
|
166 return "zh-CN" |
|
167 else: |
|
168 return code |
|
169 |
|
170 def __getTranslationModels(self): |
|
171 """ |
|
172 Private method to get the translation models supported by IBM Watson |
|
173 Language Translator. |
|
174 """ |
|
175 apiKey = self.plugin.getPreferences("IbmKey") |
|
176 if not apiKey: |
|
177 E5MessageBox.critical( |
|
178 self.__ui, |
|
179 self.tr("Error Getting Available Translations"), |
|
180 self.tr("A valid IBM Watson Language Translator key is" |
|
181 " required.") |
|
182 ) |
|
183 return |
|
184 translatorUrl = self.plugin.getPreferences("IbmUrl") |
|
185 if not translatorUrl: |
|
186 E5MessageBox.critical( |
|
187 self.__ui, |
|
188 self.tr("Error Getting Available Translations"), |
|
189 self.tr("A valid IBM Watson Language Translator URL is" |
|
190 " required.") |
|
191 ) |
|
192 return |
|
193 |
|
194 params = "?version=2018-05-01" |
|
195 url = QUrl(translatorUrl + "/v3/models" + params) |
|
196 |
|
197 extraHeaders = [ |
|
198 (b"Authorization", |
|
199 b"Basic " + QByteArray( |
|
200 b"apikey:" + apiKey.encode("utf-8")).toBase64()) |
|
201 ] |
|
202 |
|
203 from ..TranslatorRequest import TranslatorRequest |
|
204 requestObject = TranslatorRequest(self) |
|
205 response, ok = requestObject.get(url, extraHeaders=extraHeaders) |
|
206 if ok: |
|
207 response = str(response, "utf-8", "replace") |
|
208 try: |
|
209 responseDict = json.loads(response) |
|
210 except ValueError: |
|
211 E5MessageBox.critical( |
|
212 self.__ui, |
|
213 self.tr("Error Getting Available Translations"), |
|
214 self.tr("Invalid response received") |
|
215 ) |
|
216 return |
|
217 |
|
218 if "models" not in responseDict: |
|
219 E5MessageBox.critical( |
|
220 self.__ui, |
|
221 self.tr("Error Getting Available Translations"), |
|
222 self.tr("No translation available.") |
|
223 ) |
|
224 return |
|
225 |
|
226 for model in responseDict["models"]: |
|
227 if model["status"] == "available": |
|
228 source = self.__adjustLanguageCode(model["source"]) |
|
229 target = self.__adjustLanguageCode(model["target"]) |
|
230 if source not in self.__availableTranslations: |
|
231 self.__availableTranslations[source] = set() |
|
232 self.__availableTranslations[source].add(target) |