--- a/eric7/Plugins/UiExtensionPlugins/Translator/TranslatorEngines/MicrosoftEngine.py Sun Jun 12 16:05:27 2022 +0200 +++ b/eric7/Plugins/UiExtensionPlugins/Translator/TranslatorEngines/MicrosoftEngine.py Mon Jun 13 16:39:53 2022 +0200 @@ -7,7 +7,9 @@ Module implementing the Microsoft translation engine. """ -from PyQt6.QtCore import QUrl, QDateTime, QByteArray, QTimer +import json + +from PyQt6.QtCore import QUrl, QByteArray, QTimer from .TranslationEngine import TranslationEngine @@ -17,18 +19,19 @@ Class implementing the translation engine for the Microsoft translation service. """ - AccessTokenUrl = ( - "https://api.cognitive.microsoft.com/sts/v1.0/issueToken" + TranslatorUrl = ( + "https://api.cognitive.microsofttranslator.com/translate" + "?api-version=3.0" ) - TranslatorUrl = "https://api.microsofttranslator.com/V2/Http.svc/Translate" - TextToSpeechUrl = "https://api.microsofttranslator.com/V2/Http.svc/Speak" def __init__(self, plugin, parent=None): """ Constructor - @param plugin reference to the plugin object (TranslatorPlugin) - @param parent reference to the parent object (QObject) + @param plugin reference to the plugin object + @type TranslatorPlugin + @param parent reference to the parent object + @type QObject """ super().__init__(plugin, parent) @@ -43,7 +46,8 @@ """ Public method to return the name of the engine. - @return engine name (string) + @return engine name + @rtype str """ return "microsoft" @@ -51,7 +55,8 @@ """ Public method to get the supported languages. - @return list of supported language codes (list of string) + @return list of supported language codes + @rtype list of str """ return ["ar", "bg", "ca", "cs", "da", "de", "en", "es", "et", "fi", "fr", "hi", "hu", "id", @@ -60,20 +65,14 @@ "sv", "th", "tr", "uk", "vi", "zh-CN", "zh-TW", ] - def hasTTS(self): - """ - Public method indicating the Text-to-Speech capability. - - @return flag indicating the Text-to-Speech capability (boolean) - """ - return True - def __mapLanguageCode(self, code): """ Private method to map a language code to the Microsoft code. - @param code language code (string) - @return mapped language code (string) + @param code language code + @type str + @return mapped language code + @rtype str """ if code in self.__mappings: return self.__mapping[code] @@ -84,48 +83,14 @@ """ Private method to retrieve the client data. - @return tuple giving the API subscription key and a flag indicating - validity - @rtype tuple of (str, bool) + @return tuple giving the API subscription key, the API subscription + region and a flag indicating validity + @rtype tuple of (str, str, bool) """ subscriptionKey = self.plugin.getPreferences("MsTranslatorKey") - valid = bool(subscriptionKey) - return subscriptionKey, valid - - def __getAccessToken(self, requestObject): - """ - Private slot to get an access token. - - If the stored token is no longer valid, get a new one and store it. - - @param requestObject reference to the request object - (TranslatorRequest) - @return access token (string) - """ - if ( - self.plugin.getPreferences("MsAuthTokenExpire") > - QDateTime.currentDateTime() - ): - return self.plugin.getPreferences("MsAuthToken") - - # Token expired, get a new one - subscriptionKey, valid = self.__getClientDataAzure() - if not valid: - return "" - - subscriptionHeader = (b"Ocp-Apim-Subscription-Key", - subscriptionKey.encode("utf-8")) - response, ok = requestObject.post( - QUrl(self.AccessTokenUrl), QByteArray(b""), - extraHeaders=[subscriptionHeader]) - if ok: - self.plugin.setPreferences("MsAuthToken", response) - self.plugin.setPreferences( - "MsAuthTokenExpire", - QDateTime.currentDateTime().addSecs(8 * 60)) - return response - else: - return "" + subscriptionRegion = self.plugin.getPreferences("MsTranslatorRegion") + valid = bool(subscriptionKey) and bool(subscriptionRegion) + return subscriptionKey, subscriptionRegion, valid def getTranslation(self, requestObject, text, originalLanguage, translationLanguage): @@ -133,80 +98,61 @@ Public method to translate the given text. @param requestObject reference to the request object - (TranslatorRequest) - @param text text to be translated (string) - @param originalLanguage language code of the original (string) - @param translationLanguage language code of the translation (string) - @return tuple of translated text (string) and flag indicating - success (boolean) + @type TranslatorRequest + @param text text to be translated + @type str + @param originalLanguage language code of the original + @type str + @param translationLanguage language code of the translation + @type str + @return tuple of translated text and flag indicating success + @rtype tuple of (str, bool) """ - subscriptionKey, valid = self.__getClientDataAzure() + subscriptionKey, subscriptionRegion, valid = ( + self.__getClientDataAzure() + ) if not valid: return (self.tr("""You have not registered for the Microsoft""" - """ Translation service."""), + """ Azure Translation service."""), False) - accessToken = self.__getAccessToken(requestObject) - if not accessToken: - return ( - self.tr("MS Translator: No valid access token available."), - False - ) - - authHeader = (b"Authorization", - "Bearer {0}".format(accessToken).encode("utf-8")) - params = "?appid=&from={0}&to={1}&text={2}".format( + params = "&from={0}&to={1}".format( self.__mapLanguageCode(originalLanguage), self.__mapLanguageCode(translationLanguage), - text) + ) url = QUrl(self.TranslatorUrl + params) - response, ok = requestObject.get(url, extraHeaders=[authHeader]) - if ok: - response = str(response, "utf-8", "replace").strip() - if ( - response.startswith("<string") and - response.endswith("</string>") - ): - result = response.split(">", 1)[1].rsplit("<", 1)[0] - else: - result = self.tr("MS Translator: No translation available.") - ok = False - return result, ok - - def getTextToSpeechData(self, requestObject, text, language): - """ - Public method to pronounce the given text. + + requestList = [{"Text": text}] + request = QByteArray(json.dumps(requestList).encode("utf-8")) - @param requestObject reference to the request object - (TranslatorRequest) - @param text text to be pronounced (string) - @param language language code of the text (string) - @return tuple with pronounce data (QByteArray) or error string (string) - and success flag (boolean) - """ - subscriptionKey, valid = self.__getClientDataAzure() - if not valid: - return (self.tr("""You have not registered for the Microsoft""" - """ Translation service."""), - False) - - accessToken = self.__getAccessToken(requestObject) - if not accessToken: - return ( - self.tr("MS Translator: No valid access token available."), - False - ) - - params = "?language={0}&format={1}&options={2}&text={3}".format( - self.__mapLanguageCode(language), - "audio/wav", - "MaxQuality", - text) - authHeader = (b"Authorization", - "Bearer {0}".format(accessToken).encode("utf-8")) - url = QUrl(self.TextToSpeechUrl + params) - data, ok = requestObject.get(url, extraHeaders=[authHeader]) - if not ok: - data = self.tr("MS Translator: No Text-to-Speech for the selected" - " language available.") - return data, ok + headers = [ + (b"Ocp-Apim-Subscription-Key", subscriptionKey.encode("utf8")), + (b"Ocp-Apim-Subscription-Region", + subscriptionRegion.encode("utf8")), + (b"Content-Type", b"application/json; charset=UTF-8"), + (b"Content-Length", str(len(request)).encode("utf-8")), + ] + response, ok = requestObject.post( + url, request, dataType="json", extraHeaders=headers + ) + if ok: + try: + responseList = json.loads(response) + responseDict = responseList[0] + except ValueError: + return (self.tr("MS Translator: Invalid response received"), + False) + + if "translations" not in responseDict: + return (self.tr("MS Translator: No translation available."), + False) + + result = "" + translations = responseDict["translations"] + for translation in translations: + result += translation["text"] + if translation != translations[-1]: + result += "<br/>" + else: + result = response + return result, ok