We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
When editing existing strings in rich editor cursor jumps to the end
As reported on Matrix:

"When editing existing strings which have translations for "One" and "More", after each input, the cursor jumps to the end of the string."

The problem no longer appears after reverting back to f765fa994d71f6ea21c99dcd527ead4d0c4e1ea0.

--- 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         namespaces = {"a": "https://api.terminology.microsoft.com/terminology"}
316         root = ET.fromstring(r.content)
317         results = root.find(
318             ".//{http://api.terminology.microsoft.com/terminology}GetTranslationsResult"
319         )
320 
321         if results is not None:
322             for translation in results:
323                 translations.append(
324                     {
325                         "source": translation.find("a:OriginalText", namespaces).text,
326                         "target": translation.find(
327                             ".//a:TranslatedText", namespaces
328                         ).text,
329                     }
330                 )
331 
332             obj["translations"] = translations
333         return JsonResponse(obj)
334 
335     except requests.exceptions.RequestException as e:
336         return JsonResponse(
337             {"status": False, "message": f"{e}"},
338             status=r.status_code,
339         )
340
```
--- 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
@@ -128,6 +128,7 @@
     }
     body = [{"Text": text}]
 
+    r = None
     try:
         r = requests.post(url, params=payload, headers=headers, json=body)
         r.raise_for_status()
@@ -146,7 +147,7 @@
     except requests.exceptions.RequestException as e:
         return JsonResponse(
             {"status": False, "message": f"{e}"},
-            status=r.status_code,
+            status=r.status_code if r is not None else 500,
         )
 
 
@@ -207,6 +208,7 @@
         "format": "text",
     }
 
+    r = None
     try:
         r = requests.post(url, params=payload)
         r.raise_for_status()
@@ -225,7 +227,7 @@
     except requests.exceptions.RequestException as e:
         return JsonResponse(
             {"status": False, "message": f"{e}"},
-            status=r.status_code,
+            status=r.status_code if r is not None else 500,
         )
 
 
@@ -256,6 +258,7 @@
         "foinse": "gd",
     }
 
+    r = None
     try:
         r = requests.post(url, data=data)
         r.raise_for_status()
@@ -271,7 +274,7 @@
     except requests.exceptions.RequestException as e:
         return JsonResponse(
             {"status": False, "message": f"{e}"},
-            status=r.status_code,
+            status=r.status_code if r is not None else 500,
         )
 
 
@@ -307,6 +310,7 @@
 
     payload = template.render(payload)
 
+    r = None
     try:
         r = requests.post(url, data=payload, headers=headers)
         r.raise_for_status()
@@ -335,5 +339,5 @@
     except requests.exceptions.RequestException as e:
         return JsonResponse(
             {"status": False, "message": f"{e}"},
-            status=r.status_code,
+            status=r.status_code if r is not None else 500,
         )

{"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@@ -128,6 +128,7 @@\n     }\n     body = [{\"Text\": text}]\n \n+    r = None\n     try:\n         r = requests.post(url, params=payload, headers=headers, json=body)\n         r.raise_for_status()\n@@ -146,7 +147,7 @@\n     except requests.exceptions.RequestException as e:\n         return JsonResponse(\n             {\"status\": False, \"message\": f\"{e}\"},\n-            status=r.status_code,\n+            status=r.status_code if r is not None else 500,\n         )\n \n \n@@ -207,6 +208,7 @@\n         \"format\": \"text\",\n     }\n \n+    r = None\n     try:\n         r = requests.post(url, params=payload)\n         r.raise_for_status()\n@@ -225,7 +227,7 @@\n     except requests.exceptions.RequestException as e:\n         return JsonResponse(\n             {\"status\": False, \"message\": f\"{e}\"},\n-            status=r.status_code,\n+            status=r.status_code if r is not None else 500,\n         )\n \n \n@@ -256,6 +258,7 @@\n         \"foinse\": \"gd\",\n     }\n \n+    r = None\n     try:\n         r = requests.post(url, data=data)\n         r.raise_for_status()\n@@ -271,7 +274,7 @@\n     except requests.exceptions.RequestException as e:\n         return JsonResponse(\n             {\"status\": False, \"message\": f\"{e}\"},\n-            status=r.status_code,\n+            status=r.status_code if r is not None else 500,\n         )\n \n \n@@ -307,6 +310,7 @@\n \n     payload = template.render(payload)\n \n+    r = None\n     try:\n         r = requests.post(url, data=payload, headers=headers)\n         r.raise_for_status()\n@@ -335,5 +339,5 @@\n     except requests.exceptions.RequestException as e:\n         return JsonResponse(\n             {\"status\": False, \"message\": f\"{e}\"},\n-            status=r.status_code,\n+            status=r.status_code if r is not None else 500,\n         )\n", "issue": "When editing existing strings in rich editor cursor jumps to the end\nAs reported on Matrix:\r\n\r\n\"When editing existing strings which have translations for \"One\" and \"More\", after each input, the cursor jumps to the end of the string.\"\r\n\r\nThe problem no longer appears after reverting back to f765fa994d71f6ea21c99dcd527ead4d0c4e1ea0.\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        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"}], "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    r = None\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 if r is not None else 500,\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    r = None\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 if r is not None else 500,\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    r = None\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 if r is not None else 500,\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    r = None\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 if r is not None else 500,\n        )\n", "path": "pontoon/machinery/views.py"}]}