Repository: WeblateOrg/weblate

Issue:
Upgrade Microsoft translate API to v3
**Is your feature request related to a problem? Please describe.**
The v2 API currently used by Weblate is deprecated, see https://docs.microsoft.com/en-us/azure/cognitive-services/Translator/reference/v2-0-reference.

**Describe the solution you'd like**
Upgrade the API usage.

**Describe alternatives you've considered**
We can stay on v2 for now as it doesn't see to have a deadline set.

**Additional context**
* https://docs.microsoft.com/en-us/azure/cognitive-services/translator/migrate-to-v3
* https://github.com/WeblateOrg/weblate/blob/master/weblate/machinery/microsoft.py



Discussion:

Resolving patch:
diff --git a/weblate/machinery/microsoft.py b/weblate/machinery/microsoft.py
--- a/weblate/machinery/microsoft.py
+++ b/weblate/machinery/microsoft.py
@@ -25,12 +25,12 @@
 
 from weblate.machinery.base import MachineTranslation, MissingConfiguration
 
-COGNITIVE_BASE_URL = 'https://api.cognitive.microsoft.com/sts/v1.0'
-COGNITIVE_TOKEN = COGNITIVE_BASE_URL + '/issueToken?Subscription-Key={0}'
-
-BASE_URL = 'https://api.microsofttranslator.com/V2/Ajax.svc/'
-TRANSLATE_URL = BASE_URL + 'Translate'
-LIST_URL = BASE_URL + 'GetLanguagesForTranslate'
+BASE_URL = 'https://api.cognitive.microsofttranslator.com'
+TRANSLATE_URL = BASE_URL + '/translate'
+LIST_URL = BASE_URL + '/languages?api-version=3.0'
+TOKEN_URL = (
+    'https://{0}api.cognitive.microsoft.com/sts/v1.0/' 'issueToken?Subscription-Key={1}'
+)
 TOKEN_EXPIRY = timedelta(minutes=9)
 
 
@@ -56,6 +56,17 @@ def __init__(self):
         super().__init__()
         self._access_token = None
         self._token_expiry = None
+
+        # check settings for Microsoft region prefix
+        if settings.MT_MICROSOFT_REGION is None:
+            region = ""
+        else:
+            region = "{}.".format(settings.MT_MICROSOFT_REGION)
+
+        self._cognitive_token_url = TOKEN_URL.format(
+            region, settings.MT_MICROSOFT_COGNITIVE_KEY
+        )
+
         if settings.MT_MICROSOFT_COGNITIVE_KEY is None:
             raise MissingConfiguration('Microsoft Translator requires credentials')
 
@@ -72,9 +83,7 @@ def access_token(self):
         """Obtain and caches access token."""
         if self._access_token is None or self.is_token_expired():
             self._access_token = self.request(
-                "post",
-                COGNITIVE_TOKEN.format(settings.MT_MICROSOFT_COGNITIVE_KEY),
-                skip_auth=True,
+                "post", self._cognitive_token_url, skip_auth=True,
             ).text
             self._token_expiry = timezone.now() + TOKEN_EXPIRY
 
@@ -108,23 +117,24 @@ def download_languages(self):
         if isinstance(payload, str):
             raise Exception(payload)
 
-        return payload
+        return payload['translation'].keys()
 
     def download_translations(self, source, language, text, unit, user):
         """Download list of possible translations from a service."""
         args = {
-            'text': text[:5000],
+            'api-version': '3.0',
             'from': source,
             'to': language,
-            'contentType': 'text/plain',
             'category': 'general',
         }
-        response = self.request("get", TRANSLATE_URL, params=args)
+        response = self.request(
+            "post", TRANSLATE_URL, params=args, json=[{'Text': text[:5000]}]
+        )
         # Microsoft tends to use utf-8-sig instead of plain utf-8
         response.encoding = response.apparent_encoding
         payload = response.json()
         yield {
-            'text': payload,
+            'text': payload[0]['translations'][0]['text'],
             'quality': self.max_score,
             'service': self.name,
             'source': text,
diff --git a/weblate/machinery/models.py b/weblate/machinery/models.py
--- a/weblate/machinery/models.py
+++ b/weblate/machinery/models.py
@@ -42,6 +42,11 @@ class WeblateConf(AppConf):
     # https://portal.azure.com/
     MICROSOFT_COGNITIVE_KEY = None
 
+    # Microsoft Azure services region identification code see
+    # https://docs.microsoft.com/en-us/azure/cognitive-services/translator/
+    # reference/v3-0-reference#multi-service-subscription
+    MICROSOFT_REGION = None
+
     # MyMemory identification email, see
     # https://mymemory.translated.net/doc/spec.php
     MYMEMORY_EMAIL = None
diff --git a/weblate/settings_example.py b/weblate/settings_example.py
--- a/weblate/settings_example.py
+++ b/weblate/settings_example.py
@@ -520,6 +520,7 @@
 # Microsoft Cognitive Services Translator API, register at
 # https://portal.azure.com/
 MT_MICROSOFT_COGNITIVE_KEY = None
+MT_MICROSOFT_REGION = None
 
 # MyMemory identification email, see
 # https://mymemory.translated.net/doc/spec.php


Test patch:
diff --git a/weblate/machinery/tests.py b/weblate/machinery/tests.py
--- a/weblate/machinery/tests.py
+++ b/weblate/machinery/tests.py
@@ -248,6 +248,10 @@
 
 DEEPL_RESPONSE = {"translations": [{"detected_source_language": "EN", "text": "Hallo"}]}
 
+MICROSOFT_RESPONSE = [{"translations": [{"text": "Svět.", "to": "cs"}]}]
+
+MS_SUPPORTED_LANG_RESP = {"translation": {'cs': 'data', 'en': 'data', 'es': 'data'}}
+
 
 class MachineTranslationTest(TestCase):
     """Testing of machine translation core."""
@@ -388,14 +392,43 @@ def test_microsoft_cognitive(self):
         )
         responses.add(
             responses.GET,
-            'https://api.microsofttranslator.com/V2/Ajax.svc/'
-            'GetLanguagesForTranslate',
-            json=["en", "cs"],
+            'https://api.cognitive.microsofttranslator.com/'
+            'languages?api-version=3.0',
+            json=MS_SUPPORTED_LANG_RESP,
+        )
+        responses.add(
+            responses.POST,
+            'https://api.cognitive.microsofttranslator.com/'
+            'translate?api-version=3.0&from=en&to=cs&category=general',
+            json=MICROSOFT_RESPONSE,
+        )
+
+        self.assert_translate(machine)
+        self.assert_translate(machine, word='Zkouška')
+
+    @override_settings(
+        MT_MICROSOFT_COGNITIVE_KEY='KEY', MT_MICROSOFT_REGION='westeurope'
+    )
+    @responses.activate
+    def test_microsoft_cognitive_with_region(self):
+        machine = self.get_machine(MicrosoftCognitiveTranslation)
+        responses.add(
+            responses.POST,
+            'https://westeurope.api.cognitive.microsoft.com/sts/v1.0/issueToken'
+            '?Subscription-Key=KEY',
+            body='TOKEN',
         )
         responses.add(
             responses.GET,
-            'https://api.microsofttranslator.com/V2/Ajax.svc/Translate',
-            json="svět",
+            'https://api.cognitive.microsofttranslator.com/'
+            'languages?api-version=3.0',
+            json=MS_SUPPORTED_LANG_RESP,
+        )
+        responses.add(
+            responses.POST,
+            'https://api.cognitive.microsofttranslator.com/'
+            'translate?api-version=3.0&from=en&to=cs&category=general',
+            json=MICROSOFT_RESPONSE,
         )
 
         self.assert_translate(machine)