We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Dynamic dictionary for Azure AI Translator
### Describe the problem

Azure AI Translator can [translate with dynamic dictionary](https://learn.microsoft.com/en-us/azure/ai-services/translator/reference/v3-0-translate#translate-with-dynamic-dictionary). This would be a great way to expose Weblate glossaries there.

### Describe the solution you'd like

Utilize `get_glossary_terms` before sending string to translate and annotate glossary terms with `<mstrans:dictionary translation="translation of phrase">phrase</mstrans:dictionary>` markup.

### Describe alternatives you've considered

_No response_

### Screenshots

_No response_

### Additional context

_No response_

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `weblate/machinery/microsoft.py`
Content:
```
1 # Copyright © Michal Čihař <michal@weblate.org>
2 #
3 # SPDX-License-Identifier: GPL-3.0-or-later
4 
5 from __future__ import annotations
6 
7 from datetime import timedelta
8 
9 from django.utils import timezone
10 
11 from .base import MachineTranslation, MachineTranslationError
12 from .forms import MicrosoftMachineryForm
13 
14 TOKEN_URL = "https://{0}{1}/sts/v1.0/issueToken?Subscription-Key={2}"  # noqa: S105
15 TOKEN_EXPIRY = timedelta(minutes=9)
16 
17 
18 class MicrosoftCognitiveTranslation(MachineTranslation):
19     """Microsoft Cognitive Services Translator API support."""
20 
21     name = "Azure AI Translator"
22     max_score = 90
23     settings_form = MicrosoftMachineryForm
24 
25     language_map = {
26         "zh-hant": "zh-Hant",
27         "zh-hans": "zh-Hans",
28         "zh-tw": "zh-Hant",
29         "zh-cn": "zh-Hans",
30         "tlh": "tlh-Latn",
31         "tlh-qaak": "tlh-Piqd",
32         "nb": "no",
33         "bs-latn": "bs-Latn",
34         "sr": "sr-Latn",
35         "sr-latn": "sr-Latn",
36         "sr-cyrl": "sr-Cyrl",
37         "mn": "mn-Mong",
38     }
39 
40     @classmethod
41     def get_identifier(cls):
42         return "microsoft-translator"
43 
44     def __init__(self, settings: dict[str, str]):
45         """Check configuration."""
46         super().__init__(settings)
47         self._access_token = None
48         self._token_expiry = None
49 
50         # check settings for Microsoft region prefix
51         region = "" if not self.settings["region"] else f"{self.settings['region']}."
52 
53         self._cognitive_token_url = TOKEN_URL.format(
54             region,
55             self.settings["endpoint_url"],
56             self.settings["key"],
57         )
58 
59     def get_url(self, suffix):
60         return f"https://{self.settings['base_url']}/{suffix}"
61 
62     def is_token_expired(self):
63         """Check whether token is about to expire."""
64         return self._token_expiry <= timezone.now()
65 
66     def get_authentication(self):
67         """Hook for backends to allow add authentication headers to request."""
68         return {"Authorization": f"Bearer {self.access_token}"}
69 
70     @property
71     def access_token(self):
72         """Obtain and caches access token."""
73         if self._access_token is None or self.is_token_expired():
74             self._access_token = self.request(
75                 "post", self._cognitive_token_url, skip_auth=True
76             ).text
77             self._token_expiry = timezone.now() + TOKEN_EXPIRY
78 
79         return self._access_token
80 
81     def map_language_code(self, code):
82         """Convert language to service specific code."""
83         return super().map_language_code(code).replace("_", "-")
84 
85     def download_languages(self):
86         """
87         Download list of supported languages from a service.
88 
89         Example of the response:
90 
91         ['af', 'ar', 'bs-Latn', 'bg', 'ca', 'zh-CHS', 'zh-CHT', 'yue', 'hr', 'cs', 'da',
92         'nl', 'en', 'et', 'fj', 'fil', 'fi', 'fr', 'de', 'el', 'ht', 'he', 'hi', 'mww',
93         'h', 'id', 'it', 'ja', 'sw', 'tlh', 'tlh-Qaak', 'ko', 'lv', 'lt', 'mg', 'ms',
94         'mt', 'yua', 'no', 'otq', 'fa', 'pl', 'pt', 'ro', 'r', 'sm', 'sr-Cyrl',
95         'sr-Latn', 'sk', 'sl', 'es', 'sv', 'ty', 'th', 'to', 'tr', 'uk', 'ur', 'vi',
96         'cy']
97         """
98         response = self.request(
99             "get", self.get_url("languages"), params={"api-version": "3.0"}
100         )
101         # Microsoft tends to use utf-8-sig instead of plain utf-8
102         response.encoding = response.apparent_encoding
103         payload = response.json()
104 
105         # We should get an object, string usually means an error
106         if isinstance(payload, str):
107             raise MachineTranslationError(payload)
108 
109         return payload["translation"].keys()
110 
111     def download_translations(
112         self,
113         source,
114         language,
115         text: str,
116         unit,
117         user,
118         threshold: int = 75,
119     ):
120         """Download list of possible translations from a service."""
121         args = {
122             "api-version": "3.0",
123             "from": source,
124             "to": language,
125             "category": "general",
126         }
127         response = self.request(
128             "post", self.get_url("translate"), params=args, json=[{"Text": text[:5000]}]
129         )
130         # Microsoft tends to use utf-8-sig instead of plain utf-8
131         response.encoding = "utf-8-sig"
132         payload = response.json()
133         yield {
134             "text": payload[0]["translations"][0]["text"],
135             "quality": self.max_score,
136             "service": self.name,
137             "source": text,
138         }
139
```

Path: `weblate/machinery/googlev3.py`
Content:
```
1 # Copyright © Michal Čihař <michal@weblate.org>
2 #
3 # SPDX-License-Identifier: GPL-3.0-or-later
4 
5 import json
6 from typing import Any
7 
8 from django.utils.functional import cached_property
9 from google.cloud.translate import TranslationServiceClient
10 from google.oauth2 import service_account
11 
12 from .base import XMLMachineTranslationMixin
13 from .forms import GoogleV3MachineryForm
14 from .google import GoogleBaseTranslation
15 
16 
17 class GoogleV3Translation(XMLMachineTranslationMixin, GoogleBaseTranslation):
18     """Google Translate API v3 machine translation support."""
19 
20     setup = None
21     name = "Google Cloud Translation Advanced"
22     max_score = 90
23     settings_form = GoogleV3MachineryForm
24 
25     @classmethod
26     def get_identifier(cls):
27         return "google-translate-api-v3"
28 
29     @cached_property
30     def client(self):
31         credentials = service_account.Credentials.from_service_account_info(
32             json.loads(self.settings["credentials"])
33         )
34         api_endpoint = "translate.googleapis.com"
35         if self.settings["location"].startswith("europe-"):
36             api_endpoint = "translate-eu.googleapis.com"
37         elif self.settings["location"].startswith("us-"):
38             api_endpoint = "translate-us.googleapis.com"
39         return TranslationServiceClient(
40             credentials=credentials, client_options={"api_endpoint": api_endpoint}
41         )
42 
43     @cached_property
44     def parent(self):
45         project = self.settings["project"]
46         location = self.settings["location"]
47         return f"projects/{project}/locations/{location}"
48 
49     def download_languages(self):
50         """List of supported languages."""
51         response = self.client.get_supported_languages(request={"parent": self.parent})
52         return [language.language_code for language in response.languages]
53 
54     def download_translations(
55         self,
56         source,
57         language,
58         text: str,
59         unit,
60         user,
61         threshold: int = 75,
62     ):
63         """Download list of possible translations from a service."""
64         request = {
65             "parent": self.parent,
66             "contents": [text],
67             "target_language_code": language,
68             "source_language_code": source,
69             "mime_type": "text/html",
70         }
71         response = self.client.translate_text(request)
72 
73         yield {
74             "text": response.translations[0].translated_text,
75             "quality": self.max_score,
76             "service": self.name,
77             "source": text,
78         }
79 
80     def format_replacement(self, h_start: int, h_end: int, h_text: str, h_kind: Any):
81         """Generates a single replacement."""
82         return f'<span translate="no" id="{h_start}">{h_text}</span>'  # noqa: B028
83 
84     def cleanup_text(self, text, unit):
85         text, replacements = super().cleanup_text(text, unit)
86 
87         # Sanitize newlines
88         replacement = '<br translate="no">'
89         replacements[replacement] = "\n"
90 
91         return text.replace("\n", replacement), replacements
92
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/weblate/machinery/googlev3.py b/weblate/machinery/googlev3.py
--- a/weblate/machinery/googlev3.py
+++ b/weblate/machinery/googlev3.py
@@ -79,7 +79,7 @@
 
     def format_replacement(self, h_start: int, h_end: int, h_text: str, h_kind: Any):
         """Generates a single replacement."""
-        return f'<span translate="no" id="{h_start}">{h_text}</span>'  # noqa: B028
+        return f'<span translate="no" id="{h_start}">{self.escape_text(h_text)}</span>'  # noqa: B028
 
     def cleanup_text(self, text, unit):
         text, replacements = super().cleanup_text(text, unit)
diff --git a/weblate/machinery/microsoft.py b/weblate/machinery/microsoft.py
--- a/weblate/machinery/microsoft.py
+++ b/weblate/machinery/microsoft.py
@@ -5,17 +5,24 @@
 from __future__ import annotations
 
 from datetime import timedelta
+from typing import Any
 
 from django.utils import timezone
 
-from .base import MachineTranslation, MachineTranslationError
+from weblate.glossary.models import get_glossary_terms
+
+from .base import (
+    MachineTranslation,
+    MachineTranslationError,
+    XMLMachineTranslationMixin,
+)
 from .forms import MicrosoftMachineryForm
 
 TOKEN_URL = "https://{0}{1}/sts/v1.0/issueToken?Subscription-Key={2}"  # noqa: S105
 TOKEN_EXPIRY = timedelta(minutes=9)
 
 
-class MicrosoftCognitiveTranslation(MachineTranslation):
+class MicrosoftCognitiveTranslation(XMLMachineTranslationMixin, MachineTranslation):
     """Microsoft Cognitive Services Translator API support."""
 
     name = "Azure AI Translator"
@@ -123,6 +130,7 @@
             "from": source,
             "to": language,
             "category": "general",
+            "textType": "html",
         }
         response = self.request(
             "post", self.get_url("translate"), params=args, json=[{"Text": text[:5000]}]
@@ -136,3 +144,37 @@
             "service": self.name,
             "source": text,
         }
+
+    def format_replacement(self, h_start: int, h_end: int, h_text: str, h_kind: Any):
+        """Generates a single replacement."""
+        if h_kind is None:
+            return f'<span class="notranslate" id="{h_start}">{self.escape_text(h_text)}</span>'  # noqa: B028
+        # Glossary
+        flags = h_kind.all_flags
+        if "forbidden" in flags:
+            return h_text
+        if "read-only" in flags:
+            # Use terminology format
+            return self.format_replacement(h_start, h_end, h_text, None)
+        return f'<mstrans:dictionary translation="{self.escape_text(h_kind.target)}">{self.escape_text(h_text)}</mstrans:dictionary>'
+
+    def get_highlights(self, text, unit):
+        result = list(super().get_highlights(text, unit))
+
+        for term in get_glossary_terms(unit):
+            for start, end in term.glossary_positions:
+                glossary_highlight = (start, end, text[start:end], term)
+                handled = False
+                for i, (h_start, _h_end, _h_text, _h_kind) in enumerate(result):
+                    if start < h_start:
+                        if end > h_start:
+                            # Skip as overlaps
+                            break
+                        # Insert before
+                        result.insert(i, glossary_highlight)
+                        handled = True
+                        break
+                if not handled and not result or result[-1][1] < start:
+                    result.append(glossary_highlight)
+
+        yield from result

{"golden_diff": "diff --git a/weblate/machinery/googlev3.py b/weblate/machinery/googlev3.py\n--- a/weblate/machinery/googlev3.py\n+++ b/weblate/machinery/googlev3.py\n@@ -79,7 +79,7 @@\n \n     def format_replacement(self, h_start: int, h_end: int, h_text: str, h_kind: Any):\n         \"\"\"Generates a single replacement.\"\"\"\n-        return f'<span translate=\"no\" id=\"{h_start}\">{h_text}</span>'  # noqa: B028\n+        return f'<span translate=\"no\" id=\"{h_start}\">{self.escape_text(h_text)}</span>'  # noqa: B028\n \n     def cleanup_text(self, text, unit):\n         text, replacements = super().cleanup_text(text, unit)\ndiff --git a/weblate/machinery/microsoft.py b/weblate/machinery/microsoft.py\n--- a/weblate/machinery/microsoft.py\n+++ b/weblate/machinery/microsoft.py\n@@ -5,17 +5,24 @@\n from __future__ import annotations\n \n from datetime import timedelta\n+from typing import Any\n \n from django.utils import timezone\n \n-from .base import MachineTranslation, MachineTranslationError\n+from weblate.glossary.models import get_glossary_terms\n+\n+from .base import (\n+    MachineTranslation,\n+    MachineTranslationError,\n+    XMLMachineTranslationMixin,\n+)\n from .forms import MicrosoftMachineryForm\n \n TOKEN_URL = \"https://{0}{1}/sts/v1.0/issueToken?Subscription-Key={2}\"  # noqa: S105\n TOKEN_EXPIRY = timedelta(minutes=9)\n \n \n-class MicrosoftCognitiveTranslation(MachineTranslation):\n+class MicrosoftCognitiveTranslation(XMLMachineTranslationMixin, MachineTranslation):\n     \"\"\"Microsoft Cognitive Services Translator API support.\"\"\"\n \n     name = \"Azure AI Translator\"\n@@ -123,6 +130,7 @@\n             \"from\": source,\n             \"to\": language,\n             \"category\": \"general\",\n+            \"textType\": \"html\",\n         }\n         response = self.request(\n             \"post\", self.get_url(\"translate\"), params=args, json=[{\"Text\": text[:5000]}]\n@@ -136,3 +144,37 @@\n             \"service\": self.name,\n             \"source\": text,\n         }\n+\n+    def format_replacement(self, h_start: int, h_end: int, h_text: str, h_kind: Any):\n+        \"\"\"Generates a single replacement.\"\"\"\n+        if h_kind is None:\n+            return f'<span class=\"notranslate\" id=\"{h_start}\">{self.escape_text(h_text)}</span>'  # noqa: B028\n+        # Glossary\n+        flags = h_kind.all_flags\n+        if \"forbidden\" in flags:\n+            return h_text\n+        if \"read-only\" in flags:\n+            # Use terminology format\n+            return self.format_replacement(h_start, h_end, h_text, None)\n+        return f'<mstrans:dictionary translation=\"{self.escape_text(h_kind.target)}\">{self.escape_text(h_text)}</mstrans:dictionary>'\n+\n+    def get_highlights(self, text, unit):\n+        result = list(super().get_highlights(text, unit))\n+\n+        for term in get_glossary_terms(unit):\n+            for start, end in term.glossary_positions:\n+                glossary_highlight = (start, end, text[start:end], term)\n+                handled = False\n+                for i, (h_start, _h_end, _h_text, _h_kind) in enumerate(result):\n+                    if start < h_start:\n+                        if end > h_start:\n+                            # Skip as overlaps\n+                            break\n+                        # Insert before\n+                        result.insert(i, glossary_highlight)\n+                        handled = True\n+                        break\n+                if not handled and not result or result[-1][1] < start:\n+                    result.append(glossary_highlight)\n+\n+        yield from result\n", "issue": "Dynamic dictionary for Azure AI Translator\n### Describe the problem\n\nAzure AI Translator can [translate with dynamic dictionary](https://learn.microsoft.com/en-us/azure/ai-services/translator/reference/v3-0-translate#translate-with-dynamic-dictionary). This would be a great way to expose Weblate glossaries there.\n\n### Describe the solution you'd like\n\nUtilize `get_glossary_terms` before sending string to translate and annotate glossary terms with `<mstrans:dictionary translation=\"translation of phrase\">phrase</mstrans:dictionary>` markup.\n\n### Describe alternatives you've considered\n\n_No response_\n\n### Screenshots\n\n_No response_\n\n### Additional context\n\n_No response_\n", "before_files": [{"content": "# Copyright \u00a9 Michal \u010ciha\u0159 <michal@weblate.org>\n#\n# SPDX-License-Identifier: GPL-3.0-or-later\n\nfrom __future__ import annotations\n\nfrom datetime import timedelta\n\nfrom django.utils import timezone\n\nfrom .base import MachineTranslation, MachineTranslationError\nfrom .forms import MicrosoftMachineryForm\n\nTOKEN_URL = \"https://{0}{1}/sts/v1.0/issueToken?Subscription-Key={2}\"  # noqa: S105\nTOKEN_EXPIRY = timedelta(minutes=9)\n\n\nclass MicrosoftCognitiveTranslation(MachineTranslation):\n    \"\"\"Microsoft Cognitive Services Translator API support.\"\"\"\n\n    name = \"Azure AI Translator\"\n    max_score = 90\n    settings_form = MicrosoftMachineryForm\n\n    language_map = {\n        \"zh-hant\": \"zh-Hant\",\n        \"zh-hans\": \"zh-Hans\",\n        \"zh-tw\": \"zh-Hant\",\n        \"zh-cn\": \"zh-Hans\",\n        \"tlh\": \"tlh-Latn\",\n        \"tlh-qaak\": \"tlh-Piqd\",\n        \"nb\": \"no\",\n        \"bs-latn\": \"bs-Latn\",\n        \"sr\": \"sr-Latn\",\n        \"sr-latn\": \"sr-Latn\",\n        \"sr-cyrl\": \"sr-Cyrl\",\n        \"mn\": \"mn-Mong\",\n    }\n\n    @classmethod\n    def get_identifier(cls):\n        return \"microsoft-translator\"\n\n    def __init__(self, settings: dict[str, str]):\n        \"\"\"Check configuration.\"\"\"\n        super().__init__(settings)\n        self._access_token = None\n        self._token_expiry = None\n\n        # check settings for Microsoft region prefix\n        region = \"\" if not self.settings[\"region\"] else f\"{self.settings['region']}.\"\n\n        self._cognitive_token_url = TOKEN_URL.format(\n            region,\n            self.settings[\"endpoint_url\"],\n            self.settings[\"key\"],\n        )\n\n    def get_url(self, suffix):\n        return f\"https://{self.settings['base_url']}/{suffix}\"\n\n    def is_token_expired(self):\n        \"\"\"Check whether token is about to expire.\"\"\"\n        return self._token_expiry <= timezone.now()\n\n    def get_authentication(self):\n        \"\"\"Hook for backends to allow add authentication headers to request.\"\"\"\n        return {\"Authorization\": f\"Bearer {self.access_token}\"}\n\n    @property\n    def access_token(self):\n        \"\"\"Obtain and caches access token.\"\"\"\n        if self._access_token is None or self.is_token_expired():\n            self._access_token = self.request(\n                \"post\", self._cognitive_token_url, skip_auth=True\n            ).text\n            self._token_expiry = timezone.now() + TOKEN_EXPIRY\n\n        return self._access_token\n\n    def map_language_code(self, code):\n        \"\"\"Convert language to service specific code.\"\"\"\n        return super().map_language_code(code).replace(\"_\", \"-\")\n\n    def download_languages(self):\n        \"\"\"\n        Download list of supported languages from a service.\n\n        Example of the response:\n\n        ['af', 'ar', 'bs-Latn', 'bg', 'ca', 'zh-CHS', 'zh-CHT', 'yue', 'hr', 'cs', 'da',\n        'nl', 'en', 'et', 'fj', 'fil', 'fi', 'fr', 'de', 'el', 'ht', 'he', 'hi', 'mww',\n        'h', 'id', 'it', 'ja', 'sw', 'tlh', 'tlh-Qaak', 'ko', 'lv', 'lt', 'mg', 'ms',\n        'mt', 'yua', 'no', 'otq', 'fa', 'pl', 'pt', 'ro', 'r', 'sm', 'sr-Cyrl',\n        'sr-Latn', 'sk', 'sl', 'es', 'sv', 'ty', 'th', 'to', 'tr', 'uk', 'ur', 'vi',\n        'cy']\n        \"\"\"\n        response = self.request(\n            \"get\", self.get_url(\"languages\"), params={\"api-version\": \"3.0\"}\n        )\n        # Microsoft tends to use utf-8-sig instead of plain utf-8\n        response.encoding = response.apparent_encoding\n        payload = response.json()\n\n        # We should get an object, string usually means an error\n        if isinstance(payload, str):\n            raise MachineTranslationError(payload)\n\n        return payload[\"translation\"].keys()\n\n    def download_translations(\n        self,\n        source,\n        language,\n        text: str,\n        unit,\n        user,\n        threshold: int = 75,\n    ):\n        \"\"\"Download list of possible translations from a service.\"\"\"\n        args = {\n            \"api-version\": \"3.0\",\n            \"from\": source,\n            \"to\": language,\n            \"category\": \"general\",\n        }\n        response = self.request(\n            \"post\", self.get_url(\"translate\"), params=args, json=[{\"Text\": text[:5000]}]\n        )\n        # Microsoft tends to use utf-8-sig instead of plain utf-8\n        response.encoding = \"utf-8-sig\"\n        payload = response.json()\n        yield {\n            \"text\": payload[0][\"translations\"][0][\"text\"],\n            \"quality\": self.max_score,\n            \"service\": self.name,\n            \"source\": text,\n        }\n", "path": "weblate/machinery/microsoft.py"}, {"content": "# Copyright \u00a9 Michal \u010ciha\u0159 <michal@weblate.org>\n#\n# SPDX-License-Identifier: GPL-3.0-or-later\n\nimport json\nfrom typing import Any\n\nfrom django.utils.functional import cached_property\nfrom google.cloud.translate import TranslationServiceClient\nfrom google.oauth2 import service_account\n\nfrom .base import XMLMachineTranslationMixin\nfrom .forms import GoogleV3MachineryForm\nfrom .google import GoogleBaseTranslation\n\n\nclass GoogleV3Translation(XMLMachineTranslationMixin, GoogleBaseTranslation):\n    \"\"\"Google Translate API v3 machine translation support.\"\"\"\n\n    setup = None\n    name = \"Google Cloud Translation Advanced\"\n    max_score = 90\n    settings_form = GoogleV3MachineryForm\n\n    @classmethod\n    def get_identifier(cls):\n        return \"google-translate-api-v3\"\n\n    @cached_property\n    def client(self):\n        credentials = service_account.Credentials.from_service_account_info(\n            json.loads(self.settings[\"credentials\"])\n        )\n        api_endpoint = \"translate.googleapis.com\"\n        if self.settings[\"location\"].startswith(\"europe-\"):\n            api_endpoint = \"translate-eu.googleapis.com\"\n        elif self.settings[\"location\"].startswith(\"us-\"):\n            api_endpoint = \"translate-us.googleapis.com\"\n        return TranslationServiceClient(\n            credentials=credentials, client_options={\"api_endpoint\": api_endpoint}\n        )\n\n    @cached_property\n    def parent(self):\n        project = self.settings[\"project\"]\n        location = self.settings[\"location\"]\n        return f\"projects/{project}/locations/{location}\"\n\n    def download_languages(self):\n        \"\"\"List of supported languages.\"\"\"\n        response = self.client.get_supported_languages(request={\"parent\": self.parent})\n        return [language.language_code for language in response.languages]\n\n    def download_translations(\n        self,\n        source,\n        language,\n        text: str,\n        unit,\n        user,\n        threshold: int = 75,\n    ):\n        \"\"\"Download list of possible translations from a service.\"\"\"\n        request = {\n            \"parent\": self.parent,\n            \"contents\": [text],\n            \"target_language_code\": language,\n            \"source_language_code\": source,\n            \"mime_type\": \"text/html\",\n        }\n        response = self.client.translate_text(request)\n\n        yield {\n            \"text\": response.translations[0].translated_text,\n            \"quality\": self.max_score,\n            \"service\": self.name,\n            \"source\": text,\n        }\n\n    def format_replacement(self, h_start: int, h_end: int, h_text: str, h_kind: Any):\n        \"\"\"Generates a single replacement.\"\"\"\n        return f'<span translate=\"no\" id=\"{h_start}\">{h_text}</span>'  # noqa: B028\n\n    def cleanup_text(self, text, unit):\n        text, replacements = super().cleanup_text(text, unit)\n\n        # Sanitize newlines\n        replacement = '<br translate=\"no\">'\n        replacements[replacement] = \"\\n\"\n\n        return text.replace(\"\\n\", replacement), replacements\n", "path": "weblate/machinery/googlev3.py"}], "after_files": [{"content": "# Copyright \u00a9 Michal \u010ciha\u0159 <michal@weblate.org>\n#\n# SPDX-License-Identifier: GPL-3.0-or-later\n\nfrom __future__ import annotations\n\nfrom datetime import timedelta\nfrom typing import Any\n\nfrom django.utils import timezone\n\nfrom weblate.glossary.models import get_glossary_terms\n\nfrom .base import (\n    MachineTranslation,\n    MachineTranslationError,\n    XMLMachineTranslationMixin,\n)\nfrom .forms import MicrosoftMachineryForm\n\nTOKEN_URL = \"https://{0}{1}/sts/v1.0/issueToken?Subscription-Key={2}\"  # noqa: S105\nTOKEN_EXPIRY = timedelta(minutes=9)\n\n\nclass MicrosoftCognitiveTranslation(XMLMachineTranslationMixin, MachineTranslation):\n    \"\"\"Microsoft Cognitive Services Translator API support.\"\"\"\n\n    name = \"Azure AI Translator\"\n    max_score = 90\n    settings_form = MicrosoftMachineryForm\n\n    language_map = {\n        \"zh-hant\": \"zh-Hant\",\n        \"zh-hans\": \"zh-Hans\",\n        \"zh-tw\": \"zh-Hant\",\n        \"zh-cn\": \"zh-Hans\",\n        \"tlh\": \"tlh-Latn\",\n        \"tlh-qaak\": \"tlh-Piqd\",\n        \"nb\": \"no\",\n        \"bs-latn\": \"bs-Latn\",\n        \"sr\": \"sr-Latn\",\n        \"sr-latn\": \"sr-Latn\",\n        \"sr-cyrl\": \"sr-Cyrl\",\n        \"mn\": \"mn-Mong\",\n    }\n\n    @classmethod\n    def get_identifier(cls):\n        return \"microsoft-translator\"\n\n    def __init__(self, settings: dict[str, str]):\n        \"\"\"Check configuration.\"\"\"\n        super().__init__(settings)\n        self._access_token = None\n        self._token_expiry = None\n\n        # check settings for Microsoft region prefix\n        region = \"\" if not self.settings[\"region\"] else f\"{self.settings['region']}.\"\n\n        self._cognitive_token_url = TOKEN_URL.format(\n            region,\n            self.settings[\"endpoint_url\"],\n            self.settings[\"key\"],\n        )\n\n    def get_url(self, suffix):\n        return f\"https://{self.settings['base_url']}/{suffix}\"\n\n    def is_token_expired(self):\n        \"\"\"Check whether token is about to expire.\"\"\"\n        return self._token_expiry <= timezone.now()\n\n    def get_authentication(self):\n        \"\"\"Hook for backends to allow add authentication headers to request.\"\"\"\n        return {\"Authorization\": f\"Bearer {self.access_token}\"}\n\n    @property\n    def access_token(self):\n        \"\"\"Obtain and caches access token.\"\"\"\n        if self._access_token is None or self.is_token_expired():\n            self._access_token = self.request(\n                \"post\", self._cognitive_token_url, skip_auth=True\n            ).text\n            self._token_expiry = timezone.now() + TOKEN_EXPIRY\n\n        return self._access_token\n\n    def map_language_code(self, code):\n        \"\"\"Convert language to service specific code.\"\"\"\n        return super().map_language_code(code).replace(\"_\", \"-\")\n\n    def download_languages(self):\n        \"\"\"\n        Download list of supported languages from a service.\n\n        Example of the response:\n\n        ['af', 'ar', 'bs-Latn', 'bg', 'ca', 'zh-CHS', 'zh-CHT', 'yue', 'hr', 'cs', 'da',\n        'nl', 'en', 'et', 'fj', 'fil', 'fi', 'fr', 'de', 'el', 'ht', 'he', 'hi', 'mww',\n        'h', 'id', 'it', 'ja', 'sw', 'tlh', 'tlh-Qaak', 'ko', 'lv', 'lt', 'mg', 'ms',\n        'mt', 'yua', 'no', 'otq', 'fa', 'pl', 'pt', 'ro', 'r', 'sm', 'sr-Cyrl',\n        'sr-Latn', 'sk', 'sl', 'es', 'sv', 'ty', 'th', 'to', 'tr', 'uk', 'ur', 'vi',\n        'cy']\n        \"\"\"\n        response = self.request(\n            \"get\", self.get_url(\"languages\"), params={\"api-version\": \"3.0\"}\n        )\n        # Microsoft tends to use utf-8-sig instead of plain utf-8\n        response.encoding = response.apparent_encoding\n        payload = response.json()\n\n        # We should get an object, string usually means an error\n        if isinstance(payload, str):\n            raise MachineTranslationError(payload)\n\n        return payload[\"translation\"].keys()\n\n    def download_translations(\n        self,\n        source,\n        language,\n        text: str,\n        unit,\n        user,\n        threshold: int = 75,\n    ):\n        \"\"\"Download list of possible translations from a service.\"\"\"\n        args = {\n            \"api-version\": \"3.0\",\n            \"from\": source,\n            \"to\": language,\n            \"category\": \"general\",\n            \"textType\": \"html\",\n        }\n        response = self.request(\n            \"post\", self.get_url(\"translate\"), params=args, json=[{\"Text\": text[:5000]}]\n        )\n        # Microsoft tends to use utf-8-sig instead of plain utf-8\n        response.encoding = \"utf-8-sig\"\n        payload = response.json()\n        yield {\n            \"text\": payload[0][\"translations\"][0][\"text\"],\n            \"quality\": self.max_score,\n            \"service\": self.name,\n            \"source\": text,\n        }\n\n    def format_replacement(self, h_start: int, h_end: int, h_text: str, h_kind: Any):\n        \"\"\"Generates a single replacement.\"\"\"\n        if h_kind is None:\n            return f'<span class=\"notranslate\" id=\"{h_start}\">{self.escape_text(h_text)}</span>'  # noqa: B028\n        # Glossary\n        flags = h_kind.all_flags\n        if \"forbidden\" in flags:\n            return h_text\n        if \"read-only\" in flags:\n            # Use terminology format\n            return self.format_replacement(h_start, h_end, h_text, None)\n        return f'<mstrans:dictionary translation=\"{self.escape_text(h_kind.target)}\">{self.escape_text(h_text)}</mstrans:dictionary>'\n\n    def get_highlights(self, text, unit):\n        result = list(super().get_highlights(text, unit))\n\n        for term in get_glossary_terms(unit):\n            for start, end in term.glossary_positions:\n                glossary_highlight = (start, end, text[start:end], term)\n                handled = False\n                for i, (h_start, _h_end, _h_text, _h_kind) in enumerate(result):\n                    if start < h_start:\n                        if end > h_start:\n                            # Skip as overlaps\n                            break\n                        # Insert before\n                        result.insert(i, glossary_highlight)\n                        handled = True\n                        break\n                if not handled and not result or result[-1][1] < start:\n                    result.append(glossary_highlight)\n\n        yield from result\n", "path": "weblate/machinery/microsoft.py"}, {"content": "# Copyright \u00a9 Michal \u010ciha\u0159 <michal@weblate.org>\n#\n# SPDX-License-Identifier: GPL-3.0-or-later\n\nimport json\nfrom typing import Any\n\nfrom django.utils.functional import cached_property\nfrom google.cloud.translate import TranslationServiceClient\nfrom google.oauth2 import service_account\n\nfrom .base import XMLMachineTranslationMixin\nfrom .forms import GoogleV3MachineryForm\nfrom .google import GoogleBaseTranslation\n\n\nclass GoogleV3Translation(XMLMachineTranslationMixin, GoogleBaseTranslation):\n    \"\"\"Google Translate API v3 machine translation support.\"\"\"\n\n    setup = None\n    name = \"Google Cloud Translation Advanced\"\n    max_score = 90\n    settings_form = GoogleV3MachineryForm\n\n    @classmethod\n    def get_identifier(cls):\n        return \"google-translate-api-v3\"\n\n    @cached_property\n    def client(self):\n        credentials = service_account.Credentials.from_service_account_info(\n            json.loads(self.settings[\"credentials\"])\n        )\n        api_endpoint = \"translate.googleapis.com\"\n        if self.settings[\"location\"].startswith(\"europe-\"):\n            api_endpoint = \"translate-eu.googleapis.com\"\n        elif self.settings[\"location\"].startswith(\"us-\"):\n            api_endpoint = \"translate-us.googleapis.com\"\n        return TranslationServiceClient(\n            credentials=credentials, client_options={\"api_endpoint\": api_endpoint}\n        )\n\n    @cached_property\n    def parent(self):\n        project = self.settings[\"project\"]\n        location = self.settings[\"location\"]\n        return f\"projects/{project}/locations/{location}\"\n\n    def download_languages(self):\n        \"\"\"List of supported languages.\"\"\"\n        response = self.client.get_supported_languages(request={\"parent\": self.parent})\n        return [language.language_code for language in response.languages]\n\n    def download_translations(\n        self,\n        source,\n        language,\n        text: str,\n        unit,\n        user,\n        threshold: int = 75,\n    ):\n        \"\"\"Download list of possible translations from a service.\"\"\"\n        request = {\n            \"parent\": self.parent,\n            \"contents\": [text],\n            \"target_language_code\": language,\n            \"source_language_code\": source,\n            \"mime_type\": \"text/html\",\n        }\n        response = self.client.translate_text(request)\n\n        yield {\n            \"text\": response.translations[0].translated_text,\n            \"quality\": self.max_score,\n            \"service\": self.name,\n            \"source\": text,\n        }\n\n    def format_replacement(self, h_start: int, h_end: int, h_text: str, h_kind: Any):\n        \"\"\"Generates a single replacement.\"\"\"\n        return f'<span translate=\"no\" id=\"{h_start}\">{self.escape_text(h_text)}</span>'  # noqa: B028\n\n    def cleanup_text(self, text, unit):\n        text, replacements = super().cleanup_text(text, unit)\n\n        # Sanitize newlines\n        replacement = '<br translate=\"no\">'\n        replacements[replacement] = \"\\n\"\n\n        return text.replace(\"\\n\", replacement), replacements\n", "path": "weblate/machinery/googlev3.py"}]}