src/eric7/Plugins/UiExtensionPlugins/Translator/TranslatorEngines/LibreTranslateEngine.py

branch
eric7
changeset 9956
5b138f996a1e
child 10373
093dcebe5ecb
equal deleted inserted replaced
9955:aa02420279fe 9956:5b138f996a1e
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2023 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the LibreTranslate translation engine.
8 """
9
10 import contextlib
11 import json
12
13 from PyQt6.QtCore import QByteArray, QTimer, QUrl
14 from PyQt6.QtNetwork import QNetworkAccessManager, QNetworkReply, QNetworkRequest
15
16 from eric7 import Utilities
17 from eric7.EricNetwork.EricNetworkProxyFactory import proxyAuthenticationRequired
18 from eric7.EricWidgets import EricMessageBox
19
20 from .TranslationEngine import TranslationEngine
21
22
23 class LibreTranslateEngine(TranslationEngine):
24 """
25 Class implementing the translation engine for the LibreTranslate service.
26 """
27
28 # Documentation:
29 # https://de.libretranslate.com/docs/
30 #
31 # Github
32 # https://github.com/LibreTranslate/LibreTranslate
33 #
34 # Start page:
35 # http://localhost:5000
36 # https://translate.argosopentech.com (no API key required)
37
38 def __init__(self, plugin, parent=None):
39 """
40 Constructor
41
42 @param plugin reference to the plugin object
43 @type TranslatorPlugin
44 @param parent reference to the parent object
45 @type QObject
46 """
47 super().__init__(plugin, parent)
48
49 self.__ui = parent
50
51 self.__networkManager = QNetworkAccessManager(self)
52 self.__networkManager.proxyAuthenticationRequired.connect(
53 proxyAuthenticationRequired
54 )
55
56 self.__availableTranslations = {}
57 # dictionary of sets of available translations
58
59 self.__replies = []
60
61 QTimer.singleShot(0, self.__getTranslationModels)
62
63 def engineName(self):
64 """
65 Public method to return the name of the engine.
66
67 @return engine name
68 @rtype str
69 """
70 return "libre_translate"
71
72 def supportedLanguages(self):
73 """
74 Public method to get the supported languages.
75
76 @return list of supported language codes
77 @rtype list of str
78 """
79 return list(self.__availableTranslations.keys())
80
81 def supportedTargetLanguages(self, original):
82 """
83 Public method to get a list of supported target languages for an
84 original language.
85
86 @param original original language
87 @type str
88 @return list of supported target languages for the given original
89 @rtype list of str
90 """
91 targets = self.__availableTranslations.get(original, set())
92 return list(targets)
93
94 def hasTTS(self):
95 """
96 Public method indicating the Text-to-Speech capability.
97
98 @return flag indicating the Text-to-Speech capability
99 @rtype bool
100 """
101 return False
102
103 def getTranslation(
104 self, requestObject, text, originalLanguage, translationLanguage
105 ):
106 """
107 Public method to translate the given text.
108
109 @param requestObject reference to the request object
110 @type TranslatorRequest
111 @param text text to be translated
112 @type str
113 @param originalLanguage language code of the original
114 @type str
115 @param translationLanguage language code of the translation
116 @type str
117 @return tuple of translated text and flag indicating success
118 @rtype tuple of (str, bool)
119 """
120 apiKey = self.plugin.getPreferences("libreTranslateKey")
121
122 translatorUrl = self.plugin.getPreferences("LibreTranslateUrl")
123 if not translatorUrl:
124 return (
125 self.tr("LibreTranslate: A valid Language Translator URL is required."),
126 False,
127 )
128 url = QUrl(translatorUrl + "/translate")
129
130 paramsStr = "source={0}&target={1}&format=text".format(
131 originalLanguage, translationLanguage
132 )
133 if apiKey:
134 paramsStr += "&api_key={0}".format(apiKey)
135 paramsStr += "&q="
136 params = QByteArray(paramsStr.encode("utf-8"))
137 encodedText = QByteArray(
138 Utilities.html_encode(text).encode("utf-8")
139 ).toPercentEncoding()
140 request = params + encodedText
141 response, ok = requestObject.post(QUrl(url), request)
142 if ok:
143 try:
144 responseDict = json.loads(response)
145 except ValueError:
146 return self.tr("LibreTranslate: Invalid response received"), False
147
148 try:
149 return Utilities.html_encode(responseDict["translatedText"]), True
150 except KeyError:
151 return self.tr("LibreTranslate: No translation available."), False
152 else:
153 with contextlib.suppress(ValueError, KeyError):
154 responseDict = json.loads(response)
155 return responseDict["error"], False
156
157 return response, False
158
159 def __getTranslationModels(self):
160 """
161 Private method to get the translation models supported by IBM Watson
162 Language Translator.
163 """
164 translatorUrl = self.plugin.getPreferences("LibreTranslateUrl")
165 if not translatorUrl:
166 EricMessageBox.critical(
167 self.__ui,
168 self.tr("Error Getting Available Translations"),
169 self.tr("LibreTranslate: A valid Language Translator URL is required."),
170 )
171 return
172
173 url = QUrl(translatorUrl + "/languages")
174
175 extraHeaders = [(b"accept", b"application/json")]
176
177 request = QNetworkRequest(url)
178 if extraHeaders:
179 for name, value in extraHeaders:
180 request.setRawHeader(name, value)
181 reply = self.__networkManager.get(request)
182 reply.finished.connect(lambda: self.__getTranslationModelsReplyFinished(reply))
183 self.__replies.append(reply)
184
185 def __getTranslationModelsReplyFinished(self, reply):
186 """
187 Private slot handling the receipt of the available translations.
188
189 @param reply reference to the network reply object
190 @type QNetworkReply
191 """
192 if reply in self.__replies:
193 self.__replies.remove(reply)
194 reply.deleteLater()
195
196 if reply.error() != QNetworkReply.NetworkError.NoError:
197 errorStr = reply.errorString()
198 EricMessageBox.critical(
199 self.__ui,
200 self.tr("Error Getting Available Translations"),
201 self.tr(
202 "LibreTranslate: The server sent an error indication."
203 "\n Error: {0}"
204 ).format(errorStr),
205 )
206 return
207 else:
208 response = str(reply.readAll(), "utf-8", "replace")
209 try:
210 languageEntries = json.loads(response)
211 except ValueError:
212 EricMessageBox.critical(
213 self.__ui,
214 self.tr("Error Getting Available Translations"),
215 self.tr("LibreTranslate: Invalid response received"),
216 )
217 return
218
219 for languageEntry in languageEntries:
220 source = languageEntry["code"]
221 self.__availableTranslations[source] = set(languageEntry["targets"])
222
223 self.availableTranslationsLoaded.emit()
224
225
226 def createEngine(plugin, parent=None):
227 """
228 Function to instantiate a translator engine object.
229
230 @param plugin reference to the plugin object
231 @type TranslatorPlugin
232 @param parent reference to the parent object (defaults to None)
233 @type QObject (optional)
234 @return reference to the instantiated translator engine object
235 @rtype IbmWatsonEngine
236 """
237 return LibreTranslateEngine(plugin, parent=parent)

eric ide

mercurial