We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Microsoft Terminology Service returning 500
Requests to Microsoft Terminology Service API used by Machinery and Concordance Search currently return "500 Internal Server Error". Let's fix that.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `pontoon/machinery/views.py`
Content:
```
1 import json
2 import logging
3 import requests
4 import xml.etree.ElementTree as ET
5 
6 from sacremoses import MosesDetokenizer
7 from urllib.parse import quote
8 
9 from django.conf import settings
10 from django.contrib.auth.decorators import login_required
11 from django.core.paginator import EmptyPage, Paginator
12 from django.http import JsonResponse
13 from django.shortcuts import render
14 from django.template.loader import get_template
15 from django.utils.datastructures import MultiValueDictKeyError
16 
17 from pontoon.base import utils
18 from pontoon.base.models import Entity, Locale, Project, Translation
19 from pontoon.machinery.utils import (
20     get_concordance_search_data,
21     get_google_translate_data,
22     get_translation_memory_data,
23 )
24 
25 
26 log = logging.getLogger(__name__)
27 
28 
29 def machinery(request):
30     locale = utils.get_project_locale_from_request(request, Locale.objects) or "en-GB"
31 
32     return render(
33         request,
34         "machinery/machinery.html",
35         {
36             "locale": Locale.objects.get(code=locale),
37             "locales": Locale.objects.all(),
38             "is_google_translate_supported": bool(settings.GOOGLE_TRANSLATE_API_KEY),
39             "is_microsoft_translator_supported": bool(
40                 settings.MICROSOFT_TRANSLATOR_API_KEY
41             ),
42             "is_systran_translate_supported": bool(settings.SYSTRAN_TRANSLATE_API_KEY),
43         },
44     )
45 
46 
47 def translation_memory(request):
48     """Get translations from internal translations memory."""
49     try:
50         text = request.GET["text"]
51         locale = Locale.objects.get(code=request.GET["locale"])
52         pk = request.GET.get("pk", None)
53 
54         if pk is not None:
55             pk = int(pk)
56 
57     except (Locale.DoesNotExist, MultiValueDictKeyError, ValueError) as e:
58         return JsonResponse(
59             {"status": False, "message": f"Bad Request: {e}"},
60             status=400,
61         )
62 
63     data = get_translation_memory_data(text, locale, pk)
64     return JsonResponse(data, safe=False)
65 
66 
67 def concordance_search(request):
68     """Search for translations in the internal translations memory."""
69     try:
70         text = request.GET["text"]
71         locale = Locale.objects.get(code=request.GET["locale"])
72         page_results_limit = int(request.GET.get("limit", 100))
73         page = int(request.GET.get("page", 1))
74     except (Locale.DoesNotExist, MultiValueDictKeyError, ValueError) as e:
75         return JsonResponse(
76             {"status": False, "message": f"Bad Request: {e}"},
77             status=400,
78         )
79 
80     paginator = Paginator(get_concordance_search_data(text, locale), page_results_limit)
81 
82     try:
83         data = paginator.page(page)
84     except EmptyPage:
85         return JsonResponse({"results": [], "has_next": False})
86 
87     # ArrayAgg (used in get_concordance_search_data()) does not support using
88     # distinct=True in combination with ordering, so we need to do one of them
89     # manually - after pagination, to reduce the number of rows processed.
90     projects = Project.objects.order_by("disabled", "-priority").values_list(
91         "name", flat=True
92     )
93     for r in data.object_list:
94         r["project_names"] = [p for p in projects if p in r["project_names"]]
95 
96     return JsonResponse(
97         {"results": data.object_list, "has_next": data.has_next()}, safe=False
98     )
99 
100 
101 @login_required(redirect_field_name="", login_url="/403")
102 def microsoft_translator(request):
103     """Get translation from Microsoft machine translation service."""
104     try:
105         text = request.GET["text"]
106         locale_code = request.GET["locale"]
107 
108         if not locale_code:
109             raise ValueError("Locale code is empty")
110 
111         api_key = settings.MICROSOFT_TRANSLATOR_API_KEY
112         if not api_key:
113             raise ValueError("Missing api key")
114 
115     except (MultiValueDictKeyError, ValueError) as e:
116         return JsonResponse(
117             {"status": False, "message": f"Bad Request: {e}"},
118             status=400,
119         )
120 
121     url = "https://api.cognitive.microsofttranslator.com/translate"
122     headers = {"Ocp-Apim-Subscription-Key": api_key, "Content-Type": "application/json"}
123     payload = {
124         "api-version": "3.0",
125         "from": "en",
126         "to": locale_code,
127         "textType": "html",
128     }
129     body = [{"Text": text}]
130 
131     try:
132         r = requests.post(url, params=payload, headers=headers, json=body)
133         r.raise_for_status()
134 
135         root = json.loads(r.content)
136 
137         if "error" in root:
138             log.error(f"Microsoft Translator error: {root}")
139             return JsonResponse(
140                 {"status": False, "message": f"Bad Request: {root}"},
141                 status=400,
142             )
143 
144         return JsonResponse({"translation": root[0]["translations"][0]["text"]})
145 
146     except requests.exceptions.RequestException as e:
147         return JsonResponse(
148             {"status": False, "message": f"{e}"},
149             status=r.status_code,
150         )
151 
152 
153 @login_required(redirect_field_name="", login_url="/403")
154 def google_translate(request):
155     """Get translation from Google machine translation service."""
156     try:
157         text = request.GET["text"]
158         locale_code = request.GET["locale"]
159 
160         if not locale_code:
161             raise ValueError("Locale code is empty")
162 
163     except (MultiValueDictKeyError, ValueError) as e:
164         return JsonResponse(
165             {"status": False, "message": f"Bad Request: {e}"},
166             status=400,
167         )
168 
169     data = get_google_translate_data(text, locale_code)
170 
171     if not data["status"]:
172         return JsonResponse(data, status=400)
173 
174     return JsonResponse(data)
175 
176 
177 @login_required(redirect_field_name="", login_url="/403")
178 def systran_translate(request):
179     """Get translations from SYSTRAN machine translation service."""
180     try:
181         text = request.GET["text"]
182         locale_code = request.GET["locale"]
183 
184         if not locale_code:
185             raise ValueError("Locale code is empty")
186 
187         locale = Locale.objects.filter(systran_translate_code=locale_code).first()
188 
189         api_key = settings.SYSTRAN_TRANSLATE_API_KEY
190         if not api_key:
191             raise ValueError("Missing api key")
192 
193     except (Locale.DoesNotExist, MultiValueDictKeyError, ValueError) as e:
194         return JsonResponse(
195             {"status": False, "message": f"Bad Request: {e}"},
196             status=400,
197         )
198 
199     url = "https://api-translate.systran.net/translation/text/translate"
200 
201     payload = {
202         "key": api_key,
203         "input": text,
204         "source": "en",
205         "target": locale_code,
206         "profile": locale.systran_translate_profile,
207         "format": "text",
208     }
209 
210     try:
211         r = requests.post(url, params=payload)
212         r.raise_for_status()
213 
214         root = json.loads(r.content)
215 
216         if "error" in root:
217             log.error(f"SYSTRAN error: {root}")
218             return JsonResponse(
219                 {"status": False, "message": f"Bad Request: {root}"},
220                 status=400,
221             )
222 
223         return JsonResponse({"translation": root["outputs"][0]["output"]})
224 
225     except requests.exceptions.RequestException as e:
226         return JsonResponse(
227             {"status": False, "message": f"{e}"},
228             status=r.status_code,
229         )
230 
231 
232 def caighdean(request):
233     """Get translation from Caighdean machine translation service."""
234     try:
235         entityid = int(request.GET["id"])
236         entity = Entity.objects.get(id=entityid)
237     except (Entity.DoesNotExist, MultiValueDictKeyError, ValueError) as e:
238         return JsonResponse(
239             {"status": False, "message": f"Bad Request: {e}"},
240             status=400,
241         )
242 
243     try:
244         text = entity.translation_set.get(
245             locale__code="gd",
246             plural_form=None if entity.string_plural == "" else 0,
247             approved=True,
248         ).string
249     except Translation.DoesNotExist:
250         return JsonResponse({})
251 
252     url = "https://cadhan.com/api/intergaelic/3.0"
253 
254     data = {
255         "teacs": text,
256         "foinse": "gd",
257     }
258 
259     try:
260         r = requests.post(url, data=data)
261         r.raise_for_status()
262 
263         root = json.loads(r.content)
264         tokens = [x[1] for x in root]
265         translation = (
266             MosesDetokenizer().detokenize(tokens, return_str=True).replace("\\n", "\n")
267         )
268 
269         return JsonResponse({"original": text, "translation": translation})
270 
271     except requests.exceptions.RequestException as e:
272         return JsonResponse(
273             {"status": False, "message": f"{e}"},
274             status=r.status_code,
275         )
276 
277 
278 def microsoft_terminology(request):
279     """Get translations from Microsoft Terminology Service."""
280     try:
281         text = request.GET["text"]
282         locale_code = request.GET["locale"]
283 
284         if not locale_code:
285             raise ValueError("Locale code is empty")
286 
287     except (MultiValueDictKeyError, ValueError) as e:
288         return JsonResponse(
289             {"status": False, "message": f"Bad Request: {e}"},
290             status=400,
291         )
292 
293     obj = {}
294     url = "https://api.terminology.microsoft.com/Terminology.svc"
295     headers = {
296         "SOAPAction": (
297             '"http://api.terminology.microsoft.com/terminology/Terminology/GetTranslations"'
298         ),
299         "Content-Type": "text/xml; charset=utf-8",
300     }
301     payload = {
302         "text": quote(text.encode("utf-8")),
303         "to": locale_code,
304         "max_result": 5,
305     }
306     template = get_template("machinery/microsoft_terminology.jinja")
307 
308     payload = template.render(payload)
309 
310     try:
311         r = requests.post(url, data=payload, headers=headers)
312         r.raise_for_status()
313 
314         translations = []
315         xpath = ".//{http://api.terminology.microsoft.com/terminology}"
316         root = ET.fromstring(r.content)
317         results = root.find(xpath + "GetTranslationsResult")
318 
319         if results is not None:
320             for translation in results:
321                 translations.append(
322                     {
323                         "source": translation.find(xpath + "OriginalText").text,
324                         "target": translation.find(xpath + "TranslatedText").text,
325                     }
326                 )
327 
328             obj["translations"] = translations
329         return JsonResponse(obj)
330 
331     except requests.exceptions.RequestException as e:
332         return JsonResponse(
333             {"status": False, "message": f"{e}"},
334             status=r.status_code,
335         )
336
```
--- 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/pontoon/machinery/views.py b/pontoon/machinery/views.py
--- a/pontoon/machinery/views.py
+++ b/pontoon/machinery/views.py
@@ -312,16 +312,20 @@
         r.raise_for_status()
 
         translations = []
-        xpath = ".//{http://api.terminology.microsoft.com/terminology}"
+        namespaces = {"a": "https://api.terminology.microsoft.com/terminology"}
         root = ET.fromstring(r.content)
-        results = root.find(xpath + "GetTranslationsResult")
+        results = root.find(
+            ".//{http://api.terminology.microsoft.com/terminology}GetTranslationsResult"
+        )
 
         if results is not None:
             for translation in results:
                 translations.append(
                     {
-                        "source": translation.find(xpath + "OriginalText").text,
-                        "target": translation.find(xpath + "TranslatedText").text,
+                        "source": translation.find("a:OriginalText", namespaces).text,
+                        "target": translation.find(
+                            ".//a:TranslatedText", namespaces
+                        ).text,
                     }
                 )

{"golden_diff": "diff --git a/pontoon/machinery/views.py b/pontoon/machinery/views.py\n--- a/pontoon/machinery/views.py\n+++ b/pontoon/machinery/views.py\n@@ -312,16 +312,20 @@\n         r.raise_for_status()\n \n         translations = []\n-        xpath = \".//{http://api.terminology.microsoft.com/terminology}\"\n+        namespaces = {\"a\": \"https://api.terminology.microsoft.com/terminology\"}\n         root = ET.fromstring(r.content)\n-        results = root.find(xpath + \"GetTranslationsResult\")\n+        results = root.find(\n+            \".//{http://api.terminology.microsoft.com/terminology}GetTranslationsResult\"\n+        )\n \n         if results is not None:\n             for translation in results:\n                 translations.append(\n                     {\n-                        \"source\": translation.find(xpath + \"OriginalText\").text,\n-                        \"target\": translation.find(xpath + \"TranslatedText\").text,\n+                        \"source\": translation.find(\"a:OriginalText\", namespaces).text,\n+                        \"target\": translation.find(\n+                            \".//a:TranslatedText\", namespaces\n+                        ).text,\n                     }\n                 )\n", "issue": "Microsoft Terminology Service returning 500\nRequests to Microsoft Terminology Service API used by Machinery and Concordance Search currently return \"500 Internal Server Error\". Let's fix that.\n", "before_files": [{"content": "import json\nimport logging\nimport requests\nimport xml.etree.ElementTree as ET\n\nfrom sacremoses import MosesDetokenizer\nfrom urllib.parse import quote\n\nfrom django.conf import settings\nfrom django.contrib.auth.decorators import login_required\nfrom django.core.paginator import EmptyPage, Paginator\nfrom django.http import JsonResponse\nfrom django.shortcuts import render\nfrom django.template.loader import get_template\nfrom django.utils.datastructures import MultiValueDictKeyError\n\nfrom pontoon.base import utils\nfrom pontoon.base.models import Entity, Locale, Project, Translation\nfrom pontoon.machinery.utils import (\n    get_concordance_search_data,\n    get_google_translate_data,\n    get_translation_memory_data,\n)\n\n\nlog = logging.getLogger(__name__)\n\n\ndef machinery(request):\n    locale = utils.get_project_locale_from_request(request, Locale.objects) or \"en-GB\"\n\n    return render(\n        request,\n        \"machinery/machinery.html\",\n        {\n            \"locale\": Locale.objects.get(code=locale),\n            \"locales\": Locale.objects.all(),\n            \"is_google_translate_supported\": bool(settings.GOOGLE_TRANSLATE_API_KEY),\n            \"is_microsoft_translator_supported\": bool(\n                settings.MICROSOFT_TRANSLATOR_API_KEY\n            ),\n            \"is_systran_translate_supported\": bool(settings.SYSTRAN_TRANSLATE_API_KEY),\n        },\n    )\n\n\ndef translation_memory(request):\n    \"\"\"Get translations from internal translations memory.\"\"\"\n    try:\n        text = request.GET[\"text\"]\n        locale = Locale.objects.get(code=request.GET[\"locale\"])\n        pk = request.GET.get(\"pk\", None)\n\n        if pk is not None:\n            pk = int(pk)\n\n    except (Locale.DoesNotExist, MultiValueDictKeyError, ValueError) as e:\n        return JsonResponse(\n            {\"status\": False, \"message\": f\"Bad Request: {e}\"},\n            status=400,\n        )\n\n    data = get_translation_memory_data(text, locale, pk)\n    return JsonResponse(data, safe=False)\n\n\ndef concordance_search(request):\n    \"\"\"Search for translations in the internal translations memory.\"\"\"\n    try:\n        text = request.GET[\"text\"]\n        locale = Locale.objects.get(code=request.GET[\"locale\"])\n        page_results_limit = int(request.GET.get(\"limit\", 100))\n        page = int(request.GET.get(\"page\", 1))\n    except (Locale.DoesNotExist, MultiValueDictKeyError, ValueError) as e:\n        return JsonResponse(\n            {\"status\": False, \"message\": f\"Bad Request: {e}\"},\n            status=400,\n        )\n\n    paginator = Paginator(get_concordance_search_data(text, locale), page_results_limit)\n\n    try:\n        data = paginator.page(page)\n    except EmptyPage:\n        return JsonResponse({\"results\": [], \"has_next\": False})\n\n    # ArrayAgg (used in get_concordance_search_data()) does not support using\n    # distinct=True in combination with ordering, so we need to do one of them\n    # manually - after pagination, to reduce the number of rows processed.\n    projects = Project.objects.order_by(\"disabled\", \"-priority\").values_list(\n        \"name\", flat=True\n    )\n    for r in data.object_list:\n        r[\"project_names\"] = [p for p in projects if p in r[\"project_names\"]]\n\n    return JsonResponse(\n        {\"results\": data.object_list, \"has_next\": data.has_next()}, safe=False\n    )\n\n\n@login_required(redirect_field_name=\"\", login_url=\"/403\")\ndef microsoft_translator(request):\n    \"\"\"Get translation from Microsoft machine translation service.\"\"\"\n    try:\n        text = request.GET[\"text\"]\n        locale_code = request.GET[\"locale\"]\n\n        if not locale_code:\n            raise ValueError(\"Locale code is empty\")\n\n        api_key = settings.MICROSOFT_TRANSLATOR_API_KEY\n        if not api_key:\n            raise ValueError(\"Missing api key\")\n\n    except (MultiValueDictKeyError, ValueError) as e:\n        return JsonResponse(\n            {\"status\": False, \"message\": f\"Bad Request: {e}\"},\n            status=400,\n        )\n\n    url = \"https://api.cognitive.microsofttranslator.com/translate\"\n    headers = {\"Ocp-Apim-Subscription-Key\": api_key, \"Content-Type\": \"application/json\"}\n    payload = {\n        \"api-version\": \"3.0\",\n        \"from\": \"en\",\n        \"to\": locale_code,\n        \"textType\": \"html\",\n    }\n    body = [{\"Text\": text}]\n\n    try:\n        r = requests.post(url, params=payload, headers=headers, json=body)\n        r.raise_for_status()\n\n        root = json.loads(r.content)\n\n        if \"error\" in root:\n            log.error(f\"Microsoft Translator error: {root}\")\n            return JsonResponse(\n                {\"status\": False, \"message\": f\"Bad Request: {root}\"},\n                status=400,\n            )\n\n        return JsonResponse({\"translation\": root[0][\"translations\"][0][\"text\"]})\n\n    except requests.exceptions.RequestException as e:\n        return JsonResponse(\n            {\"status\": False, \"message\": f\"{e}\"},\n            status=r.status_code,\n        )\n\n\n@login_required(redirect_field_name=\"\", login_url=\"/403\")\ndef google_translate(request):\n    \"\"\"Get translation from Google machine translation service.\"\"\"\n    try:\n        text = request.GET[\"text\"]\n        locale_code = request.GET[\"locale\"]\n\n        if not locale_code:\n            raise ValueError(\"Locale code is empty\")\n\n    except (MultiValueDictKeyError, ValueError) as e:\n        return JsonResponse(\n            {\"status\": False, \"message\": f\"Bad Request: {e}\"},\n            status=400,\n        )\n\n    data = get_google_translate_data(text, locale_code)\n\n    if not data[\"status\"]:\n        return JsonResponse(data, status=400)\n\n    return JsonResponse(data)\n\n\n@login_required(redirect_field_name=\"\", login_url=\"/403\")\ndef systran_translate(request):\n    \"\"\"Get translations from SYSTRAN machine translation service.\"\"\"\n    try:\n        text = request.GET[\"text\"]\n        locale_code = request.GET[\"locale\"]\n\n        if not locale_code:\n            raise ValueError(\"Locale code is empty\")\n\n        locale = Locale.objects.filter(systran_translate_code=locale_code).first()\n\n        api_key = settings.SYSTRAN_TRANSLATE_API_KEY\n        if not api_key:\n            raise ValueError(\"Missing api key\")\n\n    except (Locale.DoesNotExist, MultiValueDictKeyError, ValueError) as e:\n        return JsonResponse(\n            {\"status\": False, \"message\": f\"Bad Request: {e}\"},\n            status=400,\n        )\n\n    url = \"https://api-translate.systran.net/translation/text/translate\"\n\n    payload = {\n        \"key\": api_key,\n        \"input\": text,\n        \"source\": \"en\",\n        \"target\": locale_code,\n        \"profile\": locale.systran_translate_profile,\n        \"format\": \"text\",\n    }\n\n    try:\n        r = requests.post(url, params=payload)\n        r.raise_for_status()\n\n        root = json.loads(r.content)\n\n        if \"error\" in root:\n            log.error(f\"SYSTRAN error: {root}\")\n            return JsonResponse(\n                {\"status\": False, \"message\": f\"Bad Request: {root}\"},\n                status=400,\n            )\n\n        return JsonResponse({\"translation\": root[\"outputs\"][0][\"output\"]})\n\n    except requests.exceptions.RequestException as e:\n        return JsonResponse(\n            {\"status\": False, \"message\": f\"{e}\"},\n            status=r.status_code,\n        )\n\n\ndef caighdean(request):\n    \"\"\"Get translation from Caighdean machine translation service.\"\"\"\n    try:\n        entityid = int(request.GET[\"id\"])\n        entity = Entity.objects.get(id=entityid)\n    except (Entity.DoesNotExist, MultiValueDictKeyError, ValueError) as e:\n        return JsonResponse(\n            {\"status\": False, \"message\": f\"Bad Request: {e}\"},\n            status=400,\n        )\n\n    try:\n        text = entity.translation_set.get(\n            locale__code=\"gd\",\n            plural_form=None if entity.string_plural == \"\" else 0,\n            approved=True,\n        ).string\n    except Translation.DoesNotExist:\n        return JsonResponse({})\n\n    url = \"https://cadhan.com/api/intergaelic/3.0\"\n\n    data = {\n        \"teacs\": text,\n        \"foinse\": \"gd\",\n    }\n\n    try:\n        r = requests.post(url, data=data)\n        r.raise_for_status()\n\n        root = json.loads(r.content)\n        tokens = [x[1] for x in root]\n        translation = (\n            MosesDetokenizer().detokenize(tokens, return_str=True).replace(\"\\\\n\", \"\\n\")\n        )\n\n        return JsonResponse({\"original\": text, \"translation\": translation})\n\n    except requests.exceptions.RequestException as e:\n        return JsonResponse(\n            {\"status\": False, \"message\": f\"{e}\"},\n            status=r.status_code,\n        )\n\n\ndef microsoft_terminology(request):\n    \"\"\"Get translations from Microsoft Terminology Service.\"\"\"\n    try:\n        text = request.GET[\"text\"]\n        locale_code = request.GET[\"locale\"]\n\n        if not locale_code:\n            raise ValueError(\"Locale code is empty\")\n\n    except (MultiValueDictKeyError, ValueError) as e:\n        return JsonResponse(\n            {\"status\": False, \"message\": f\"Bad Request: {e}\"},\n            status=400,\n        )\n\n    obj = {}\n    url = \"https://api.terminology.microsoft.com/Terminology.svc\"\n    headers = {\n        \"SOAPAction\": (\n            '\"http://api.terminology.microsoft.com/terminology/Terminology/GetTranslations\"'\n        ),\n        \"Content-Type\": \"text/xml; charset=utf-8\",\n    }\n    payload = {\n        \"text\": quote(text.encode(\"utf-8\")),\n        \"to\": locale_code,\n        \"max_result\": 5,\n    }\n    template = get_template(\"machinery/microsoft_terminology.jinja\")\n\n    payload = template.render(payload)\n\n    try:\n        r = requests.post(url, data=payload, headers=headers)\n        r.raise_for_status()\n\n        translations = []\n        xpath = \".//{http://api.terminology.microsoft.com/terminology}\"\n        root = ET.fromstring(r.content)\n        results = root.find(xpath + \"GetTranslationsResult\")\n\n        if results is not None:\n            for translation in results:\n                translations.append(\n                    {\n                        \"source\": translation.find(xpath + \"OriginalText\").text,\n                        \"target\": translation.find(xpath + \"TranslatedText\").text,\n                    }\n                )\n\n            obj[\"translations\"] = translations\n        return JsonResponse(obj)\n\n    except requests.exceptions.RequestException as e:\n        return JsonResponse(\n            {\"status\": False, \"message\": f\"{e}\"},\n            status=r.status_code,\n        )\n", "path": "pontoon/machinery/views.py"}], "after_files": [{"content": "import json\nimport logging\nimport requests\nimport xml.etree.ElementTree as ET\n\nfrom sacremoses import MosesDetokenizer\nfrom urllib.parse import quote\n\nfrom django.conf import settings\nfrom django.contrib.auth.decorators import login_required\nfrom django.core.paginator import EmptyPage, Paginator\nfrom django.http import JsonResponse\nfrom django.shortcuts import render\nfrom django.template.loader import get_template\nfrom django.utils.datastructures import MultiValueDictKeyError\n\nfrom pontoon.base import utils\nfrom pontoon.base.models import Entity, Locale, Project, Translation\nfrom pontoon.machinery.utils import (\n    get_concordance_search_data,\n    get_google_translate_data,\n    get_translation_memory_data,\n)\n\n\nlog = logging.getLogger(__name__)\n\n\ndef machinery(request):\n    locale = utils.get_project_locale_from_request(request, Locale.objects) or \"en-GB\"\n\n    return render(\n        request,\n        \"machinery/machinery.html\",\n        {\n            \"locale\": Locale.objects.get(code=locale),\n            \"locales\": Locale.objects.all(),\n            \"is_google_translate_supported\": bool(settings.GOOGLE_TRANSLATE_API_KEY),\n            \"is_microsoft_translator_supported\": bool(\n                settings.MICROSOFT_TRANSLATOR_API_KEY\n            ),\n            \"is_systran_translate_supported\": bool(settings.SYSTRAN_TRANSLATE_API_KEY),\n        },\n    )\n\n\ndef translation_memory(request):\n    \"\"\"Get translations from internal translations memory.\"\"\"\n    try:\n        text = request.GET[\"text\"]\n        locale = Locale.objects.get(code=request.GET[\"locale\"])\n        pk = request.GET.get(\"pk\", None)\n\n        if pk is not None:\n            pk = int(pk)\n\n    except (Locale.DoesNotExist, MultiValueDictKeyError, ValueError) as e:\n        return JsonResponse(\n            {\"status\": False, \"message\": f\"Bad Request: {e}\"},\n            status=400,\n        )\n\n    data = get_translation_memory_data(text, locale, pk)\n    return JsonResponse(data, safe=False)\n\n\ndef concordance_search(request):\n    \"\"\"Search for translations in the internal translations memory.\"\"\"\n    try:\n        text = request.GET[\"text\"]\n        locale = Locale.objects.get(code=request.GET[\"locale\"])\n        page_results_limit = int(request.GET.get(\"limit\", 100))\n        page = int(request.GET.get(\"page\", 1))\n    except (Locale.DoesNotExist, MultiValueDictKeyError, ValueError) as e:\n        return JsonResponse(\n            {\"status\": False, \"message\": f\"Bad Request: {e}\"},\n            status=400,\n        )\n\n    paginator = Paginator(get_concordance_search_data(text, locale), page_results_limit)\n\n    try:\n        data = paginator.page(page)\n    except EmptyPage:\n        return JsonResponse({\"results\": [], \"has_next\": False})\n\n    # ArrayAgg (used in get_concordance_search_data()) does not support using\n    # distinct=True in combination with ordering, so we need to do one of them\n    # manually - after pagination, to reduce the number of rows processed.\n    projects = Project.objects.order_by(\"disabled\", \"-priority\").values_list(\n        \"name\", flat=True\n    )\n    for r in data.object_list:\n        r[\"project_names\"] = [p for p in projects if p in r[\"project_names\"]]\n\n    return JsonResponse(\n        {\"results\": data.object_list, \"has_next\": data.has_next()}, safe=False\n    )\n\n\n@login_required(redirect_field_name=\"\", login_url=\"/403\")\ndef microsoft_translator(request):\n    \"\"\"Get translation from Microsoft machine translation service.\"\"\"\n    try:\n        text = request.GET[\"text\"]\n        locale_code = request.GET[\"locale\"]\n\n        if not locale_code:\n            raise ValueError(\"Locale code is empty\")\n\n        api_key = settings.MICROSOFT_TRANSLATOR_API_KEY\n        if not api_key:\n            raise ValueError(\"Missing api key\")\n\n    except (MultiValueDictKeyError, ValueError) as e:\n        return JsonResponse(\n            {\"status\": False, \"message\": f\"Bad Request: {e}\"},\n            status=400,\n        )\n\n    url = \"https://api.cognitive.microsofttranslator.com/translate\"\n    headers = {\"Ocp-Apim-Subscription-Key\": api_key, \"Content-Type\": \"application/json\"}\n    payload = {\n        \"api-version\": \"3.0\",\n        \"from\": \"en\",\n        \"to\": locale_code,\n        \"textType\": \"html\",\n    }\n    body = [{\"Text\": text}]\n\n    try:\n        r = requests.post(url, params=payload, headers=headers, json=body)\n        r.raise_for_status()\n\n        root = json.loads(r.content)\n\n        if \"error\" in root:\n            log.error(f\"Microsoft Translator error: {root}\")\n            return JsonResponse(\n                {\"status\": False, \"message\": f\"Bad Request: {root}\"},\n                status=400,\n            )\n\n        return JsonResponse({\"translation\": root[0][\"translations\"][0][\"text\"]})\n\n    except requests.exceptions.RequestException as e:\n        return JsonResponse(\n            {\"status\": False, \"message\": f\"{e}\"},\n            status=r.status_code,\n        )\n\n\n@login_required(redirect_field_name=\"\", login_url=\"/403\")\ndef google_translate(request):\n    \"\"\"Get translation from Google machine translation service.\"\"\"\n    try:\n        text = request.GET[\"text\"]\n        locale_code = request.GET[\"locale\"]\n\n        if not locale_code:\n            raise ValueError(\"Locale code is empty\")\n\n    except (MultiValueDictKeyError, ValueError) as e:\n        return JsonResponse(\n            {\"status\": False, \"message\": f\"Bad Request: {e}\"},\n            status=400,\n        )\n\n    data = get_google_translate_data(text, locale_code)\n\n    if not data[\"status\"]:\n        return JsonResponse(data, status=400)\n\n    return JsonResponse(data)\n\n\n@login_required(redirect_field_name=\"\", login_url=\"/403\")\ndef systran_translate(request):\n    \"\"\"Get translations from SYSTRAN machine translation service.\"\"\"\n    try:\n        text = request.GET[\"text\"]\n        locale_code = request.GET[\"locale\"]\n\n        if not locale_code:\n            raise ValueError(\"Locale code is empty\")\n\n        locale = Locale.objects.filter(systran_translate_code=locale_code).first()\n\n        api_key = settings.SYSTRAN_TRANSLATE_API_KEY\n        if not api_key:\n            raise ValueError(\"Missing api key\")\n\n    except (Locale.DoesNotExist, MultiValueDictKeyError, ValueError) as e:\n        return JsonResponse(\n            {\"status\": False, \"message\": f\"Bad Request: {e}\"},\n            status=400,\n        )\n\n    url = \"https://api-translate.systran.net/translation/text/translate\"\n\n    payload = {\n        \"key\": api_key,\n        \"input\": text,\n        \"source\": \"en\",\n        \"target\": locale_code,\n        \"profile\": locale.systran_translate_profile,\n        \"format\": \"text\",\n    }\n\n    try:\n        r = requests.post(url, params=payload)\n        r.raise_for_status()\n\n        root = json.loads(r.content)\n\n        if \"error\" in root:\n            log.error(f\"SYSTRAN error: {root}\")\n            return JsonResponse(\n                {\"status\": False, \"message\": f\"Bad Request: {root}\"},\n                status=400,\n            )\n\n        return JsonResponse({\"translation\": root[\"outputs\"][0][\"output\"]})\n\n    except requests.exceptions.RequestException as e:\n        return JsonResponse(\n            {\"status\": False, \"message\": f\"{e}\"},\n            status=r.status_code,\n        )\n\n\ndef caighdean(request):\n    \"\"\"Get translation from Caighdean machine translation service.\"\"\"\n    try:\n        entityid = int(request.GET[\"id\"])\n        entity = Entity.objects.get(id=entityid)\n    except (Entity.DoesNotExist, MultiValueDictKeyError, ValueError) as e:\n        return JsonResponse(\n            {\"status\": False, \"message\": f\"Bad Request: {e}\"},\n            status=400,\n        )\n\n    try:\n        text = entity.translation_set.get(\n            locale__code=\"gd\",\n            plural_form=None if entity.string_plural == \"\" else 0,\n            approved=True,\n        ).string\n    except Translation.DoesNotExist:\n        return JsonResponse({})\n\n    url = \"https://cadhan.com/api/intergaelic/3.0\"\n\n    data = {\n        \"teacs\": text,\n        \"foinse\": \"gd\",\n    }\n\n    try:\n        r = requests.post(url, data=data)\n        r.raise_for_status()\n\n        root = json.loads(r.content)\n        tokens = [x[1] for x in root]\n        translation = (\n            MosesDetokenizer().detokenize(tokens, return_str=True).replace(\"\\\\n\", \"\\n\")\n        )\n\n        return JsonResponse({\"original\": text, \"translation\": translation})\n\n    except requests.exceptions.RequestException as e:\n        return JsonResponse(\n            {\"status\": False, \"message\": f\"{e}\"},\n            status=r.status_code,\n        )\n\n\ndef microsoft_terminology(request):\n    \"\"\"Get translations from Microsoft Terminology Service.\"\"\"\n    try:\n        text = request.GET[\"text\"]\n        locale_code = request.GET[\"locale\"]\n\n        if not locale_code:\n            raise ValueError(\"Locale code is empty\")\n\n    except (MultiValueDictKeyError, ValueError) as e:\n        return JsonResponse(\n            {\"status\": False, \"message\": f\"Bad Request: {e}\"},\n            status=400,\n        )\n\n    obj = {}\n    url = \"https://api.terminology.microsoft.com/Terminology.svc\"\n    headers = {\n        \"SOAPAction\": (\n            '\"http://api.terminology.microsoft.com/terminology/Terminology/GetTranslations\"'\n        ),\n        \"Content-Type\": \"text/xml; charset=utf-8\",\n    }\n    payload = {\n        \"text\": quote(text.encode(\"utf-8\")),\n        \"to\": locale_code,\n        \"max_result\": 5,\n    }\n    template = get_template(\"machinery/microsoft_terminology.jinja\")\n\n    payload = template.render(payload)\n\n    try:\n        r = requests.post(url, data=payload, headers=headers)\n        r.raise_for_status()\n\n        translations = []\n        namespaces = {\"a\": \"https://api.terminology.microsoft.com/terminology\"}\n        root = ET.fromstring(r.content)\n        results = root.find(\n            \".//{http://api.terminology.microsoft.com/terminology}GetTranslationsResult\"\n        )\n\n        if results is not None:\n            for translation in results:\n                translations.append(\n                    {\n                        \"source\": translation.find(\"a:OriginalText\", namespaces).text,\n                        \"target\": translation.find(\n                            \".//a:TranslatedText\", namespaces\n                        ).text,\n                    }\n                )\n\n            obj[\"translations\"] = translations\n        return JsonResponse(obj)\n\n    except requests.exceptions.RequestException as e:\n        return JsonResponse(\n            {\"status\": False, \"message\": f\"{e}\"},\n            status=r.status_code,\n        )\n", "path": "pontoon/machinery/views.py"}]}