eric7/Plugins/UiExtensionPlugins/Translator/TranslatorEngines/IbmWatsonEngine.py

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

eric ide

mercurial