24 class IbmWatsonEngine(TranslationEngine): |
22 class IbmWatsonEngine(TranslationEngine): |
25 """ |
23 """ |
26 Class implementing the translation engine for the IBM Watson Language |
24 Class implementing the translation engine for the IBM Watson Language |
27 Translator service. |
25 Translator service. |
28 """ |
26 """ |
|
27 |
29 # Documentation: |
28 # Documentation: |
30 # https://www.ibm.com/watson/developercloud/language-translator |
29 # https://www.ibm.com/watson/developercloud/language-translator |
31 # |
30 # |
32 # Start page: |
31 # Start page: |
33 # https://www.ibm.com/watson/services/language-translator/ |
32 # https://www.ibm.com/watson/services/language-translator/ |
34 |
33 |
35 def __init__(self, plugin, parent=None): |
34 def __init__(self, plugin, parent=None): |
36 """ |
35 """ |
37 Constructor |
36 Constructor |
38 |
37 |
39 @param plugin reference to the plugin object |
38 @param plugin reference to the plugin object |
40 @type TranslatorPlugin |
39 @type TranslatorPlugin |
41 @param parent reference to the parent object |
40 @param parent reference to the parent object |
42 @type QObject |
41 @type QObject |
43 """ |
42 """ |
44 super().__init__(plugin, parent) |
43 super().__init__(plugin, parent) |
45 |
44 |
46 self.__ui = parent |
45 self.__ui = parent |
47 |
46 |
48 self.__networkManager = QNetworkAccessManager(self) |
47 self.__networkManager = QNetworkAccessManager(self) |
49 self.__networkManager.proxyAuthenticationRequired.connect( |
48 self.__networkManager.proxyAuthenticationRequired.connect( |
50 proxyAuthenticationRequired) |
49 proxyAuthenticationRequired |
51 |
50 ) |
|
51 |
52 self.__availableTranslations = {} |
52 self.__availableTranslations = {} |
53 # dictionary of sets of available translations |
53 # dictionary of sets of available translations |
54 |
54 |
55 self.__replies = [] |
55 self.__replies = [] |
56 |
56 |
57 QTimer.singleShot(0, self.__getTranslationModels) |
57 QTimer.singleShot(0, self.__getTranslationModels) |
58 |
58 |
59 def engineName(self): |
59 def engineName(self): |
60 """ |
60 """ |
61 Public method to return the name of the engine. |
61 Public method to return the name of the engine. |
62 |
62 |
63 @return engine name |
63 @return engine name |
64 @rtype str |
64 @rtype str |
65 """ |
65 """ |
66 return "ibm_watson" |
66 return "ibm_watson" |
67 |
67 |
68 def supportedLanguages(self): |
68 def supportedLanguages(self): |
69 """ |
69 """ |
70 Public method to get the supported languages. |
70 Public method to get the supported languages. |
71 |
71 |
72 @return list of supported language codes |
72 @return list of supported language codes |
73 @rtype list of str |
73 @rtype list of str |
74 """ |
74 """ |
75 return list(self.__availableTranslations.keys()) |
75 return list(self.__availableTranslations.keys()) |
76 |
76 |
77 def supportedTargetLanguages(self, original): |
77 def supportedTargetLanguages(self, original): |
78 """ |
78 """ |
79 Public method to get a list of supported target languages for an |
79 Public method to get a list of supported target languages for an |
80 original language. |
80 original language. |
81 |
81 |
82 @param original original language |
82 @param original original language |
83 @type str |
83 @type str |
84 @return list of supported target languages for the given original |
84 @return list of supported target languages for the given original |
85 @rtype list of str |
85 @rtype list of str |
86 """ |
86 """ |
87 targets = self.__availableTranslations.get(original, set()) |
87 targets = self.__availableTranslations.get(original, set()) |
88 return list(targets) |
88 return list(targets) |
89 |
89 |
90 def hasTTS(self): |
90 def hasTTS(self): |
91 """ |
91 """ |
92 Public method indicating the Text-to-Speech capability. |
92 Public method indicating the Text-to-Speech capability. |
93 |
93 |
94 @return flag indicating the Text-to-Speech capability |
94 @return flag indicating the Text-to-Speech capability |
95 @rtype bool |
95 @rtype bool |
96 """ |
96 """ |
97 return False |
97 return False |
98 |
98 |
99 def getTranslation(self, requestObject, text, originalLanguage, |
99 def getTranslation( |
100 translationLanguage): |
100 self, requestObject, text, originalLanguage, translationLanguage |
|
101 ): |
101 """ |
102 """ |
102 Public method to translate the given text. |
103 Public method to translate the given text. |
103 |
104 |
104 @param requestObject reference to the request object |
105 @param requestObject reference to the request object |
105 @type TranslatorRequest |
106 @type TranslatorRequest |
106 @param text text to be translated |
107 @param text text to be translated |
107 @type str |
108 @type str |
108 @param originalLanguage language code of the original |
109 @param originalLanguage language code of the original |
112 @return tuple of translated text and flag indicating success |
113 @return tuple of translated text and flag indicating success |
113 @rtype tuple of (str, bool) |
114 @rtype tuple of (str, bool) |
114 """ |
115 """ |
115 apiKey = self.plugin.getPreferences("IbmKey") |
116 apiKey = self.plugin.getPreferences("IbmKey") |
116 if not apiKey: |
117 if not apiKey: |
117 return self.tr("IBM Watson: A valid Language Translator key is" |
118 return ( |
118 " required."), False |
119 self.tr("IBM Watson: A valid Language Translator key is" " required."), |
|
120 False, |
|
121 ) |
119 translatorUrl = self.plugin.getPreferences("IbmUrl") |
122 translatorUrl = self.plugin.getPreferences("IbmUrl") |
120 if not translatorUrl: |
123 if not translatorUrl: |
121 return self.tr("IBM Watson: A valid Language Translator URL is" |
124 return ( |
122 " required."), False |
125 self.tr("IBM Watson: A valid Language Translator URL is" " required."), |
123 |
126 False, |
|
127 ) |
|
128 |
124 params = "?version=2018-05-01" |
129 params = "?version=2018-05-01" |
125 url = QUrl(translatorUrl + "/v3/translate" + params) |
130 url = QUrl(translatorUrl + "/v3/translate" + params) |
126 |
131 |
127 requestDict = { |
132 requestDict = { |
128 "text": [text], |
133 "text": [text], |
129 "source": originalLanguage, |
134 "source": originalLanguage, |
130 "target": translationLanguage, |
135 "target": translationLanguage, |
131 } |
136 } |
132 request = QByteArray(json.dumps(requestDict).encode("utf-8")) |
137 request = QByteArray(json.dumps(requestDict).encode("utf-8")) |
133 |
138 |
134 extraHeaders = [ |
139 extraHeaders = [ |
135 (b"Authorization", |
140 ( |
136 b"Basic " + QByteArray( |
141 b"Authorization", |
137 b"apikey:" + apiKey.encode("utf-8")).toBase64()) |
142 b"Basic " + QByteArray(b"apikey:" + apiKey.encode("utf-8")).toBase64(), |
|
143 ) |
138 ] |
144 ] |
139 |
145 |
140 response, ok = requestObject.post(url, request, dataType="json", |
146 response, ok = requestObject.post( |
141 extraHeaders=extraHeaders) |
147 url, request, dataType="json", extraHeaders=extraHeaders |
|
148 ) |
142 if ok: |
149 if ok: |
143 try: |
150 try: |
144 responseDict = json.loads(response) |
151 responseDict = json.loads(response) |
145 except ValueError: |
152 except ValueError: |
146 return self.tr("IBM Watson: Invalid response received"), False |
153 return self.tr("IBM Watson: Invalid response received"), False |
147 |
154 |
148 if "translations" not in responseDict: |
155 if "translations" not in responseDict: |
149 return self.tr("IBM Watson: No translation available."), False |
156 return self.tr("IBM Watson: No translation available."), False |
150 |
157 |
151 result = "" |
158 result = "" |
152 translations = responseDict["translations"] |
159 translations = responseDict["translations"] |
153 for translation in translations: |
160 for translation in translations: |
154 result += translation["translation"] |
161 result += translation["translation"] |
155 if translation != translations[-1]: |
162 if translation != translations[-1]: |
156 result += "<br/>" |
163 result += "<br/>" |
157 else: |
164 else: |
158 result = response |
165 result = response |
159 return result, ok |
166 return result, ok |
160 |
167 |
161 def __adjustLanguageCode(self, code): |
168 def __adjustLanguageCode(self, code): |
162 """ |
169 """ |
163 Private method to adjust a given language code. |
170 Private method to adjust a given language code. |
164 |
171 |
165 @param code code to be adjusted |
172 @param code code to be adjusted |
166 @type str |
173 @type str |
167 @return adjusted language code |
174 @return adjusted language code |
168 @rtype str |
175 @rtype str |
169 """ |
176 """ |
170 if code == "zh": |
177 if code == "zh": |
171 return "zh-CN" |
178 return "zh-CN" |
172 else: |
179 else: |
173 return code |
180 return code |
174 |
181 |
175 def __getTranslationModels(self): |
182 def __getTranslationModels(self): |
176 """ |
183 """ |
177 Private method to get the translation models supported by IBM Watson |
184 Private method to get the translation models supported by IBM Watson |
178 Language Translator. |
185 Language Translator. |
179 """ |
186 """ |
180 apiKey = self.plugin.getPreferences("IbmKey") |
187 apiKey = self.plugin.getPreferences("IbmKey") |
181 if not apiKey: |
188 if not apiKey: |
182 EricMessageBox.critical( |
189 EricMessageBox.critical( |
183 self.__ui, |
190 self.__ui, |
184 self.tr("Error Getting Available Translations"), |
191 self.tr("Error Getting Available Translations"), |
185 self.tr("IBM Watson: A valid Language Translator key is" |
192 self.tr("IBM Watson: A valid Language Translator key is" " required."), |
186 " required.") |
|
187 ) |
193 ) |
188 return |
194 return |
189 translatorUrl = self.plugin.getPreferences("IbmUrl") |
195 translatorUrl = self.plugin.getPreferences("IbmUrl") |
190 if not translatorUrl: |
196 if not translatorUrl: |
191 EricMessageBox.critical( |
197 EricMessageBox.critical( |
192 self.__ui, |
198 self.__ui, |
193 self.tr("Error Getting Available Translations"), |
199 self.tr("Error Getting Available Translations"), |
194 self.tr("IBM Watson: A valid Language Translator URL is" |
200 self.tr("IBM Watson: A valid Language Translator URL is" " required."), |
195 " required.") |
|
196 ) |
201 ) |
197 return |
202 return |
198 |
203 |
199 params = "?version=2018-05-01" |
204 params = "?version=2018-05-01" |
200 url = QUrl(translatorUrl + "/v3/models" + params) |
205 url = QUrl(translatorUrl + "/v3/models" + params) |
201 |
206 |
202 extraHeaders = [ |
207 extraHeaders = [ |
203 (b"Authorization", |
208 ( |
204 b"Basic " + QByteArray( |
209 b"Authorization", |
205 b"apikey:" + apiKey.encode("utf-8")).toBase64()) |
210 b"Basic " + QByteArray(b"apikey:" + apiKey.encode("utf-8")).toBase64(), |
|
211 ) |
206 ] |
212 ] |
207 |
213 |
208 request = QNetworkRequest(url) |
214 request = QNetworkRequest(url) |
209 if extraHeaders: |
215 if extraHeaders: |
210 for name, value in extraHeaders: |
216 for name, value in extraHeaders: |
211 request.setRawHeader(name, value) |
217 request.setRawHeader(name, value) |
212 reply = self.__networkManager.get(request) |
218 reply = self.__networkManager.get(request) |
213 reply.finished.connect( |
219 reply.finished.connect(lambda: self.__getTranslationModelsReplyFinished(reply)) |
214 lambda: self.__getTranslationModelsReplyFinished(reply)) |
|
215 self.__replies.append(reply) |
220 self.__replies.append(reply) |
216 |
221 |
217 def __getTranslationModelsReplyFinished(self, reply): |
222 def __getTranslationModelsReplyFinished(self, reply): |
218 """ |
223 """ |
219 Private slot handling the receipt of the available translations. |
224 Private slot handling the receipt of the available translations. |
220 |
225 |
221 @param reply reference to the network reply object |
226 @param reply reference to the network reply object |
222 @type QNetworkReply |
227 @type QNetworkReply |
223 """ |
228 """ |
224 if reply in self.__replies: |
229 if reply in self.__replies: |
225 self.__replies.remove(reply) |
230 self.__replies.remove(reply) |
226 reply.deleteLater() |
231 reply.deleteLater() |
227 |
232 |
228 if reply.error() != QNetworkReply.NetworkError.NoError: |
233 if reply.error() != QNetworkReply.NetworkError.NoError: |
229 errorStr = reply.errorString() |
234 errorStr = reply.errorString() |
230 EricMessageBox.critical( |
235 EricMessageBox.critical( |
231 self.__ui, |
236 self.__ui, |
232 self.tr("Error Getting Available Translations"), |
237 self.tr("Error Getting Available Translations"), |
233 self.tr("IBM Watson: The server sent an error indication." |
238 self.tr( |
234 "\n Error: {0}").format(errorStr) |
239 "IBM Watson: The server sent an error indication." |
|
240 "\n Error: {0}" |
|
241 ).format(errorStr), |
235 ) |
242 ) |
236 return |
243 return |
237 else: |
244 else: |
238 response = str(reply.readAll(), "utf-8", "replace") |
245 response = str(reply.readAll(), "utf-8", "replace") |
239 try: |
246 try: |
240 responseDict = json.loads(response) |
247 responseDict = json.loads(response) |
241 except ValueError: |
248 except ValueError: |
242 EricMessageBox.critical( |
249 EricMessageBox.critical( |
243 self.__ui, |
250 self.__ui, |
244 self.tr("Error Getting Available Translations"), |
251 self.tr("Error Getting Available Translations"), |
245 self.tr("IBM Watson: Invalid response received") |
252 self.tr("IBM Watson: Invalid response received"), |
246 ) |
253 ) |
247 return |
254 return |
248 |
255 |
249 if "models" not in responseDict: |
256 if "models" not in responseDict: |
250 EricMessageBox.critical( |
257 EricMessageBox.critical( |
251 self.__ui, |
258 self.__ui, |
252 self.tr("Error Getting Available Translations"), |
259 self.tr("Error Getting Available Translations"), |
253 self.tr("IBM Watson: No translation available.") |
260 self.tr("IBM Watson: No translation available."), |
254 ) |
261 ) |
255 return |
262 return |
256 |
263 |
257 for model in responseDict["models"]: |
264 for model in responseDict["models"]: |
258 if model["status"] == "available": |
265 if model["status"] == "available": |
259 source = self.__adjustLanguageCode(model["source"]) |
266 source = self.__adjustLanguageCode(model["source"]) |
260 target = self.__adjustLanguageCode(model["target"]) |
267 target = self.__adjustLanguageCode(model["target"]) |
261 if source not in self.__availableTranslations: |
268 if source not in self.__availableTranslations: |
262 self.__availableTranslations[source] = set() |
269 self.__availableTranslations[source] = set() |
263 self.__availableTranslations[source].add(target) |
270 self.__availableTranslations[source].add(target) |
264 |
271 |
265 self.availableTranslationsLoaded.emit() |
272 self.availableTranslationsLoaded.emit() |