From 3cc068052629014b3396f1ae8dbce0423aa0631b Mon Sep 17 00:00:00 2001 From: Halit Celik Date: Mon, 10 Jun 2024 14:39:37 +0200 Subject: [PATCH 1/5] Wraps and unwraps django variables in xml tags for deepl not to translate them --- rosetta/tests/tests.py | 38 +++++ rosetta/translate_utils.py | 44 +++++- .../test_47_2_deeps_ajax_translation.yaml | 2 +- ...deepl_ajax_translation_with_variables.yaml | 146 ++++++++++++++++++ 4 files changed, 226 insertions(+), 4 deletions(-) create mode 100644 testproject/fixtures/vcr_cassettes/test_deepl_ajax_translation_with_variables.yaml diff --git a/rosetta/tests/tests.py b/rosetta/tests/tests.py index ece2fc6..549f58b 100644 --- a/rosetta/tests/tests.py +++ b/rosetta/tests/tests.py @@ -1009,6 +1009,44 @@ def test_47_2_deeps_ajax_translation(self): ) self.assertContains(r, '"Salut tout le monde"') + @vcr.use_cassette( + "fixtures/vcr_cassettes/test_deepl_ajax_translation_with_variables.yaml", + match_on=["method", "scheme", "port", "path", "query", "raw_body"], + record_mode="once", + ) + @override_settings( + DEEPL_AUTH_KEY="FAKE", + AZURE_CLIENT_SECRET=None, + ) + def test_deepl_ajax_translation_with_variables(self): + cases = { + "de": "Es gibt %(items)d %(name)s verfügbar.", + "it": "Ci sono %(items)d %(name)s disponibili.", + "pt": "Há %(items)d %(name)s disponíveis.", + } + for lang, text in cases.items(): + r = self.client.get( + reverse("rosetta.translate_text") + f"?from={lang}&to=en&text={text}" + ) + self.assertEqual( + r.json().get("translation"), "There are %(items)d %(name)s available." + ) + + def test_formating_text_to_and_from_deepl(self): + from ..translate_utils import format_text + + samples = [ + "Es gibt %(items)d %(name)s verfügbar.", + "Ci sono %(items)d %(name)s disponibili.", + "Há %(items)d %(name)s disponíveis.", + "Stokta %(items)d %(name)s var.", + ] + for sample in samples: + to_deepl = format_text(sample, "to_deepl") + from_deepl = format_text(to_deepl, "from_deepl") + back_to_deepl = format_text(from_deepl, "to_deepl") + self.assertEqual(to_deepl, back_to_deepl) + @override_settings(ROSETTA_REQUIRES_AUTH=True) def test_48_requires_auth_not_respected_issue_203(self): self.client.logout() diff --git a/rosetta/translate_utils.py b/rosetta/translate_utils.py index b4f2b52..a9a7b1f 100644 --- a/rosetta/translate_utils.py +++ b/rosetta/translate_utils.py @@ -1,6 +1,6 @@ import json +import re import uuid - import requests from django.conf import settings @@ -47,7 +47,41 @@ def translate(text, from_language, to_language): raise TranslationException("No translation API service is configured.") +def format_text(text, direction="to_deepl"): + # TODO find a better name + if direction == "to_deepl": + pattern = r"%\((\w+)\)(\w)" + + def replace_variable(match): + # Our pattern will always catch 2 groups, the first group being '%(' + # Second group being ')d' or ')s' + variable = match.group(1) + type_specifier = match.group(2) + if variable and type_specifier: + return f"{variable}{type_specifier}" + else: + raise TranslationException("Badly formatted variable in translation") + + return re.sub(pattern, replace_variable, text) + else: + return ( + text.replace("", "") + .replace("", "") + .replace("", "%(") + .replace("", ")") + ) + + def translate_by_deepl(text, to_language, auth_key): + """ + This method connects to the translator Deepl API and fetches a response with translations. + :param text: The source text to be translated + :param to_language: The target language to translate the text into + Wraps variables in tags and instructs Deepl not to translate those. + Then from Deepl response, converts back these tags to django variable syntax. + %(name)s becomes names and back to %(name) in the response text. + :return: Returns the response from the Deepl as a python object. + """ if auth_key.lower().endswith(":fx"): endpoint = "https://api-free.deepl.com" else: @@ -57,11 +91,15 @@ def translate_by_deepl(text, to_language, auth_key): f"{endpoint}/v2/translate", headers={"Authorization": f"DeepL-Auth-Key {auth_key}"}, data={ + "tag_handling": "xml", + "ignore_tags": "var", "target_lang": to_language.upper(), - "text": text, + "text": format_text(text, "to_deepl"), }, ) - return r.json().get("translations")[0].get("text") + result = r.json().get("translations")[0].get("text") + + return format_text(result, "from_deepl") def translate_by_azure(text, from_language, to_language, subscription_key): diff --git a/testproject/fixtures/vcr_cassettes/test_47_2_deeps_ajax_translation.yaml b/testproject/fixtures/vcr_cassettes/test_47_2_deeps_ajax_translation.yaml index bab63a5..6e1d899 100644 --- a/testproject/fixtures/vcr_cassettes/test_47_2_deeps_ajax_translation.yaml +++ b/testproject/fixtures/vcr_cassettes/test_47_2_deeps_ajax_translation.yaml @@ -1,6 +1,6 @@ interactions: - request: - body: target_lang=FR&text=hello+world + body: tag_handling=xml&ignore_tags=var&target_lang=FR&text=hello+world headers: Accept: - '*/*' diff --git a/testproject/fixtures/vcr_cassettes/test_deepl_ajax_translation_with_variables.yaml b/testproject/fixtures/vcr_cassettes/test_deepl_ajax_translation_with_variables.yaml new file mode 100644 index 0000000..3a008f7 --- /dev/null +++ b/testproject/fixtures/vcr_cassettes/test_deepl_ajax_translation_with_variables.yaml @@ -0,0 +1,146 @@ +interactions: +- request: + body: tag_handling=xml&ignore_tags=var&target_lang=EN&text=Es+gibt+%3Cvar%3Eitems%3C%2Fvar%3E%3Ctype%3E%3Cvar%3Ed%3C%2Fvar%3E%3C%2Ftype%3E+%3Cvar%3Ename%3C%2Fvar%3E%3Ctype%3E%3Cvar%3Es%3C%2Fvar%3E%3C%2Ftype%3E+verf%C3%BCgbar. + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - DeepL-Auth-Key FAKE + Connection: + - keep-alive + Content-Length: + - '219' + Content-Type: + - application/x-www-form-urlencoded + User-Agent: + - python-requests/2.32.3 + method: POST + uri: https://api-free.deepl.com/v2/translate + response: + body: + string: '{"translations":[{"detected_source_language":"DE","text":"There are + itemsd names + available."}]}' + headers: + access-control-allow-origin: + - '*' + access-control-expose-headers: + - Server-Timing, X-Trace-ID + content-encoding: + - gzip + content-type: + - application/json + date: + - Mon, 10 Jun 2024 11:47:17 GMT + server-timing: + - l7_lb_tls;dur=99, l7_lb_idle;dur=4, l7_lb_receive;dur=2, l7_lb_total;dur=165 + strict-transport-security: + - max-age=63072000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - Accept-Encoding + x-trace-id: + - ceec50b6cca24e9cbeed2bd1bdf4435a + status: + code: 200 + message: OK +- request: + body: tag_handling=xml&ignore_tags=var&target_lang=EN&text=Ci+sono+%3Cvar%3Eitems%3C%2Fvar%3E%3Ctype%3E%3Cvar%3Ed%3C%2Fvar%3E%3C%2Ftype%3E+%3Cvar%3Ename%3C%2Fvar%3E%3Ctype%3E%3Cvar%3Es%3C%2Fvar%3E%3C%2Ftype%3E+disponibili. + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - DeepL-Auth-Key FAKE + Connection: + - keep-alive + Content-Length: + - '216' + Content-Type: + - application/x-www-form-urlencoded + User-Agent: + - python-requests/2.32.3 + method: POST + uri: https://api-free.deepl.com/v2/translate + response: + body: + string: '{"translations":[{"detected_source_language":"IT","text":"There are + itemsd names + available."}]}' + headers: + access-control-allow-origin: + - '*' + access-control-expose-headers: + - Server-Timing, X-Trace-ID + content-encoding: + - gzip + content-type: + - application/json + date: + - Mon, 10 Jun 2024 11:47:17 GMT + server-timing: + - l7_lb_tls;dur=112, l7_lb_idle;dur=0, l7_lb_receive;dur=0, l7_lb_total;dur=215 + strict-transport-security: + - max-age=63072000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - Accept-Encoding + x-trace-id: + - c40d4791453848babe7024b55da74cba + status: + code: 200 + message: OK +- request: + body: tag_handling=xml&ignore_tags=var&target_lang=EN&text=H%C3%A1+%3Cvar%3Eitems%3C%2Fvar%3E%3Ctype%3E%3Cvar%3Ed%3C%2Fvar%3E%3C%2Ftype%3E+%3Cvar%3Ename%3C%2Fvar%3E%3Ctype%3E%3Cvar%3Es%3C%2Fvar%3E%3C%2Ftype%3E+dispon%C3%ADveis. + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - DeepL-Auth-Key FAKE + Connection: + - keep-alive + Content-Length: + - '221' + Content-Type: + - application/x-www-form-urlencoded + User-Agent: + - python-requests/2.32.3 + method: POST + uri: https://api-free.deepl.com/v2/translate + response: + body: + string: '{"translations":[{"detected_source_language":"PT","text":"There are + itemsd names + available."}]}' + headers: + access-control-allow-origin: + - '*' + access-control-expose-headers: + - Server-Timing, X-Trace-ID + content-encoding: + - gzip + content-type: + - application/json + date: + - Mon, 10 Jun 2024 11:47:17 GMT + server-timing: + - l7_lb_tls;dur=101, l7_lb_idle;dur=4, l7_lb_receive;dur=1, l7_lb_total;dur=216 + strict-transport-security: + - max-age=63072000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - Accept-Encoding + x-trace-id: + - 7f1111989f574859b3db10f1bafa4efb + status: + code: 200 + message: OK +version: 1 From 7e1c1cf491c895dd8ac3b0b27132e64fb476606f Mon Sep 17 00:00:00 2001 From: Halit Celik Date: Mon, 10 Jun 2024 15:07:57 +0200 Subject: [PATCH 2/5] remove content encodig gzip from vcr cassettes --- .../test_deepl_ajax_translation_with_variables.yaml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/testproject/fixtures/vcr_cassettes/test_deepl_ajax_translation_with_variables.yaml b/testproject/fixtures/vcr_cassettes/test_deepl_ajax_translation_with_variables.yaml index 3a008f7..c228510 100644 --- a/testproject/fixtures/vcr_cassettes/test_deepl_ajax_translation_with_variables.yaml +++ b/testproject/fixtures/vcr_cassettes/test_deepl_ajax_translation_with_variables.yaml @@ -28,8 +28,6 @@ interactions: - '*' access-control-expose-headers: - Server-Timing, X-Trace-ID - content-encoding: - - gzip content-type: - application/json date: @@ -76,8 +74,6 @@ interactions: - '*' access-control-expose-headers: - Server-Timing, X-Trace-ID - content-encoding: - - gzip content-type: - application/json date: @@ -124,8 +120,6 @@ interactions: - '*' access-control-expose-headers: - Server-Timing, X-Trace-ID - content-encoding: - - gzip content-type: - application/json date: From 2dbc232f434238fbcae278a549a813b8ef6ba2f6 Mon Sep 17 00:00:00 2001 From: Halit Celik Date: Mon, 30 Sep 2024 13:26:05 +0200 Subject: [PATCH 3/5] send variable type as html attribute --- rosetta/static/admin/rosetta/js/rosetta.js | 2 +- rosetta/tests/tests.py | 22 --- rosetta/translate_utils.py | 15 +- ...deepl_ajax_translation_with_variables.yaml | 140 ------------------ 4 files changed, 8 insertions(+), 171 deletions(-) delete mode 100644 testproject/fixtures/vcr_cassettes/test_deepl_ajax_translation_with_variables.yaml diff --git a/rosetta/static/admin/rosetta/js/rosetta.js b/rosetta/static/admin/rosetta/js/rosetta.js index a33cae3..ee216cd 100644 --- a/rosetta/static/admin/rosetta/js/rosetta.js +++ b/rosetta/static/admin/rosetta/js/rosetta.js @@ -19,7 +19,7 @@ $(document).ready(function() { var sourceLang = rosetta_settings.MESSAGES_SOURCE_LANGUAGE_CODE; var destLang = rosetta_settings.rosetta_i18n_lang_code_normalized; - orig = unescape(orig).replace(//g,'\n').replace(//,'').replace(/<\/code>/g,'').replace(/>/g,'>').replace(/</g,'<'); + orig = unescape(orig).replace(//g,'\n').replace(//g,'').replace(/<\/code>/g,'').replace(/>/g,'>').replace(/</g,'<'); a.attr('class','suggesting').html('...'); $.getJSON(rosetta_settings.translate_text_url, { diff --git a/rosetta/tests/tests.py b/rosetta/tests/tests.py index 549f58b..4d4c709 100644 --- a/rosetta/tests/tests.py +++ b/rosetta/tests/tests.py @@ -1009,28 +1009,6 @@ def test_47_2_deeps_ajax_translation(self): ) self.assertContains(r, '"Salut tout le monde"') - @vcr.use_cassette( - "fixtures/vcr_cassettes/test_deepl_ajax_translation_with_variables.yaml", - match_on=["method", "scheme", "port", "path", "query", "raw_body"], - record_mode="once", - ) - @override_settings( - DEEPL_AUTH_KEY="FAKE", - AZURE_CLIENT_SECRET=None, - ) - def test_deepl_ajax_translation_with_variables(self): - cases = { - "de": "Es gibt %(items)d %(name)s verfügbar.", - "it": "Ci sono %(items)d %(name)s disponibili.", - "pt": "Há %(items)d %(name)s disponíveis.", - } - for lang, text in cases.items(): - r = self.client.get( - reverse("rosetta.translate_text") + f"?from={lang}&to=en&text={text}" - ) - self.assertEqual( - r.json().get("translation"), "There are %(items)d %(name)s available." - ) def test_formating_text_to_and_from_deepl(self): from ..translate_utils import format_text diff --git a/rosetta/translate_utils.py b/rosetta/translate_utils.py index e733071..3cae8e9 100644 --- a/rosetta/translate_utils.py +++ b/rosetta/translate_utils.py @@ -46,7 +46,6 @@ def translate(text, from_language, to_language): else: raise TranslationException("No translation API service is configured.") - def format_text(text, direction="to_deepl"): # TODO find a better name if direction == "to_deepl": @@ -58,18 +57,18 @@ def replace_variable(match): variable = match.group(1) type_specifier = match.group(2) if variable and type_specifier: - return f"{variable}{type_specifier}" + return f'{variable}' else: raise TranslationException("Badly formatted variable in translation") return re.sub(pattern, replace_variable, text) else: - return ( - text.replace("", "") - .replace("", "") - .replace("", "%(") - .replace("", ")") - ) + + for g in re.finditer(r'.*?(?P[0-9a-zA-Z_]+).*?', text): + t, v = g.groups() + text = text.replace(f'{v}', f"%({v}){t}") + return text + def translate_by_deepl(text, to_language, auth_key): diff --git a/testproject/fixtures/vcr_cassettes/test_deepl_ajax_translation_with_variables.yaml b/testproject/fixtures/vcr_cassettes/test_deepl_ajax_translation_with_variables.yaml deleted file mode 100644 index c228510..0000000 --- a/testproject/fixtures/vcr_cassettes/test_deepl_ajax_translation_with_variables.yaml +++ /dev/null @@ -1,140 +0,0 @@ -interactions: -- request: - body: tag_handling=xml&ignore_tags=var&target_lang=EN&text=Es+gibt+%3Cvar%3Eitems%3C%2Fvar%3E%3Ctype%3E%3Cvar%3Ed%3C%2Fvar%3E%3C%2Ftype%3E+%3Cvar%3Ename%3C%2Fvar%3E%3Ctype%3E%3Cvar%3Es%3C%2Fvar%3E%3C%2Ftype%3E+verf%C3%BCgbar. - headers: - Accept: - - '*/*' - Accept-Encoding: - - gzip, deflate - Authorization: - - DeepL-Auth-Key FAKE - Connection: - - keep-alive - Content-Length: - - '219' - Content-Type: - - application/x-www-form-urlencoded - User-Agent: - - python-requests/2.32.3 - method: POST - uri: https://api-free.deepl.com/v2/translate - response: - body: - string: '{"translations":[{"detected_source_language":"DE","text":"There are - itemsd names - available."}]}' - headers: - access-control-allow-origin: - - '*' - access-control-expose-headers: - - Server-Timing, X-Trace-ID - content-type: - - application/json - date: - - Mon, 10 Jun 2024 11:47:17 GMT - server-timing: - - l7_lb_tls;dur=99, l7_lb_idle;dur=4, l7_lb_receive;dur=2, l7_lb_total;dur=165 - strict-transport-security: - - max-age=63072000; includeSubDomains; preload - transfer-encoding: - - chunked - vary: - - Accept-Encoding - x-trace-id: - - ceec50b6cca24e9cbeed2bd1bdf4435a - status: - code: 200 - message: OK -- request: - body: tag_handling=xml&ignore_tags=var&target_lang=EN&text=Ci+sono+%3Cvar%3Eitems%3C%2Fvar%3E%3Ctype%3E%3Cvar%3Ed%3C%2Fvar%3E%3C%2Ftype%3E+%3Cvar%3Ename%3C%2Fvar%3E%3Ctype%3E%3Cvar%3Es%3C%2Fvar%3E%3C%2Ftype%3E+disponibili. - headers: - Accept: - - '*/*' - Accept-Encoding: - - gzip, deflate - Authorization: - - DeepL-Auth-Key FAKE - Connection: - - keep-alive - Content-Length: - - '216' - Content-Type: - - application/x-www-form-urlencoded - User-Agent: - - python-requests/2.32.3 - method: POST - uri: https://api-free.deepl.com/v2/translate - response: - body: - string: '{"translations":[{"detected_source_language":"IT","text":"There are - itemsd names - available."}]}' - headers: - access-control-allow-origin: - - '*' - access-control-expose-headers: - - Server-Timing, X-Trace-ID - content-type: - - application/json - date: - - Mon, 10 Jun 2024 11:47:17 GMT - server-timing: - - l7_lb_tls;dur=112, l7_lb_idle;dur=0, l7_lb_receive;dur=0, l7_lb_total;dur=215 - strict-transport-security: - - max-age=63072000; includeSubDomains; preload - transfer-encoding: - - chunked - vary: - - Accept-Encoding - x-trace-id: - - c40d4791453848babe7024b55da74cba - status: - code: 200 - message: OK -- request: - body: tag_handling=xml&ignore_tags=var&target_lang=EN&text=H%C3%A1+%3Cvar%3Eitems%3C%2Fvar%3E%3Ctype%3E%3Cvar%3Ed%3C%2Fvar%3E%3C%2Ftype%3E+%3Cvar%3Ename%3C%2Fvar%3E%3Ctype%3E%3Cvar%3Es%3C%2Fvar%3E%3C%2Ftype%3E+dispon%C3%ADveis. - headers: - Accept: - - '*/*' - Accept-Encoding: - - gzip, deflate - Authorization: - - DeepL-Auth-Key FAKE - Connection: - - keep-alive - Content-Length: - - '221' - Content-Type: - - application/x-www-form-urlencoded - User-Agent: - - python-requests/2.32.3 - method: POST - uri: https://api-free.deepl.com/v2/translate - response: - body: - string: '{"translations":[{"detected_source_language":"PT","text":"There are - itemsd names - available."}]}' - headers: - access-control-allow-origin: - - '*' - access-control-expose-headers: - - Server-Timing, X-Trace-ID - content-type: - - application/json - date: - - Mon, 10 Jun 2024 11:47:17 GMT - server-timing: - - l7_lb_tls;dur=101, l7_lb_idle;dur=4, l7_lb_receive;dur=1, l7_lb_total;dur=216 - strict-transport-security: - - max-age=63072000; includeSubDomains; preload - transfer-encoding: - - chunked - vary: - - Accept-Encoding - x-trace-id: - - 7f1111989f574859b3db10f1bafa4efb - status: - code: 200 - message: OK -version: 1 From b32840d9b4f33093df393e3bf593df7859067c14 Mon Sep 17 00:00:00 2001 From: Halit Celik Date: Mon, 7 Oct 2024 10:41:56 +0200 Subject: [PATCH 4/5] rename function and fix tests --- rosetta/tests/tests.py | 26 ++++++++-- rosetta/translate_utils.py | 48 +++++++++---------- testproject/.venv/lib64 | 1 + testproject/.venv/pyvenv.cfg | 5 ++ ...deepl_ajax_translation_with_variables.yaml | 46 ++++++++++++++++++ 5 files changed, 97 insertions(+), 29 deletions(-) create mode 120000 testproject/.venv/lib64 create mode 100644 testproject/.venv/pyvenv.cfg create mode 100644 testproject/fixtures/vcr_cassettes/test_deepl_ajax_translation_with_variables.yaml diff --git a/rosetta/tests/tests.py b/rosetta/tests/tests.py index 4d4c709..2b9cce9 100644 --- a/rosetta/tests/tests.py +++ b/rosetta/tests/tests.py @@ -1009,9 +1009,27 @@ def test_47_2_deeps_ajax_translation(self): ) self.assertContains(r, '"Salut tout le monde"') + @vcr.use_cassette( + "fixtures/vcr_cassettes/test_deepl_ajax_translation_with_variables.yaml", + match_on=["method", "scheme", "port", "path", "query", "raw_body"], + record_mode="once", + ) + @override_settings( + DEEPL_AUTH_KEY="f03e38a2-f9ff-68da-b635-322047985051:fx", + AZURE_CLIENT_SECRET=None, + ) + def test_deepl_ajax_translation_with_variables(self): + text = "Ci sono %(items)d %(name)s disponibili." + r = self.client.get( + reverse("rosetta.translate_text") + f"?from=it&to=en&text={text}" + ) + self.assertEqual( + r.json().get("translation"), "There are %(items)d %(name)s available." + ) + def test_formating_text_to_and_from_deepl(self): - from ..translate_utils import format_text + from ..translate_utils import format_text_to_deepl, format_text_from_deepl samples = [ "Es gibt %(items)d %(name)s verfügbar.", @@ -1020,9 +1038,9 @@ def test_formating_text_to_and_from_deepl(self): "Stokta %(items)d %(name)s var.", ] for sample in samples: - to_deepl = format_text(sample, "to_deepl") - from_deepl = format_text(to_deepl, "from_deepl") - back_to_deepl = format_text(from_deepl, "to_deepl") + to_deepl = format_text_to_deepl(sample) + from_deepl = format_text_from_deepl(to_deepl) + back_to_deepl = format_text_to_deepl(from_deepl) self.assertEqual(to_deepl, back_to_deepl) @override_settings(ROSETTA_REQUIRES_AUTH=True) diff --git a/rosetta/translate_utils.py b/rosetta/translate_utils.py index 3cae8e9..307d32d 100644 --- a/rosetta/translate_utils.py +++ b/rosetta/translate_utils.py @@ -46,28 +46,25 @@ def translate(text, from_language, to_language): else: raise TranslationException("No translation API service is configured.") -def format_text(text, direction="to_deepl"): - # TODO find a better name - if direction == "to_deepl": - pattern = r"%\((\w+)\)(\w)" - - def replace_variable(match): - # Our pattern will always catch 2 groups, the first group being '%(' - # Second group being ')d' or ')s' - variable = match.group(1) - type_specifier = match.group(2) - if variable and type_specifier: - return f'{variable}' - else: - raise TranslationException("Badly formatted variable in translation") - - return re.sub(pattern, replace_variable, text) - else: - - for g in re.finditer(r'.*?(?P[0-9a-zA-Z_]+).*?', text): - t, v = g.groups() - text = text.replace(f'{v}', f"%({v}){t}") - return text +def format_text_to_deepl(text): + pattern = r"%\((\w+)\)(\w)" + def replace_variable(match): + # Our pattern will always catch 2 groups, the first group being '%(' + # Second group being ')d' or ')s' + variable = match.group(1) + type_specifier = match.group(2) + if variable and type_specifier: + return f'{variable}' + else: + raise TranslationException("Badly formatted variable in translation") + + return re.sub(pattern, replace_variable, text) + +def format_text_from_deepl(text): + for g in re.finditer(r'.*?(?P[0-9a-zA-Z_]+).*?', text): + t, v = g.groups() + text = text.replace(f'{v}', f"%({v}){t}") + return text @@ -78,7 +75,7 @@ def translate_by_deepl(text, to_language, auth_key): :param to_language: The target language to translate the text into Wraps variables in tags and instructs Deepl not to translate those. Then from Deepl response, converts back these tags to django variable syntax. - %(name)s becomes names and back to %(name) in the response text. + %(name)s becomes name and back to %(name)s in the response text. :return: Returns the response from the Deepl as a python object. """ if auth_key.lower().endswith(":fx"): @@ -93,15 +90,16 @@ def translate_by_deepl(text, to_language, auth_key): "tag_handling": "xml", "ignore_tags": "var", "target_lang": to_language.upper(), - "text": format_text(text, "to_deepl"), + "text": format_text_to_deepl(text), }, ) if r.status_code != 200: raise TranslationException( f"Deepl response is {r.status_code}. Please check your API key or try again later." ) + try: - return format_text(r.json().get("translations")[0].get("text"), "from_deepl") + return format_text_from_deepl(r.json().get("translations")[0].get("text")) except Exception: raise TranslationException("Deepl returned a non-JSON or unexpected response.") diff --git a/testproject/.venv/lib64 b/testproject/.venv/lib64 new file mode 120000 index 0000000..7951405 --- /dev/null +++ b/testproject/.venv/lib64 @@ -0,0 +1 @@ +lib \ No newline at end of file diff --git a/testproject/.venv/pyvenv.cfg b/testproject/.venv/pyvenv.cfg new file mode 100644 index 0000000..44e63bb --- /dev/null +++ b/testproject/.venv/pyvenv.cfg @@ -0,0 +1,5 @@ +home = /home/halit/.pyenv/versions/3.12.3/bin +include-system-site-packages = false +version = 3.12.3 +executable = /home/halit/.pyenv/versions/3.12.3/bin/python3.12 +command = /home/halit/.pyenv/versions/3.12.3/bin/python -m venv /home/halit/Code/django-rosetta/testproject/.venv diff --git a/testproject/fixtures/vcr_cassettes/test_deepl_ajax_translation_with_variables.yaml b/testproject/fixtures/vcr_cassettes/test_deepl_ajax_translation_with_variables.yaml new file mode 100644 index 0000000..f38661a --- /dev/null +++ b/testproject/fixtures/vcr_cassettes/test_deepl_ajax_translation_with_variables.yaml @@ -0,0 +1,46 @@ +interactions: +- request: + body: tag_handling=xml&ignore_tags=var&target_lang=EN&text=Ci+sono+%3Cvar+type%3D%22d%22%3Eitems%3C%2Fvar%3E+%3Cvar+type%3D%22s%22%3Ename%3C%2Fvar%3E+disponibili. + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - DeepL-Auth-Key f03e38a2-f9ff-68da-b635-322047985051:fx + Connection: + - keep-alive + Content-Length: + - '156' + Content-Type: + - application/x-www-form-urlencoded + User-Agent: + - python-requests/2.32.3 + method: POST + uri: https://api-free.deepl.com/v2/translate + response: + body: + string: '{"translations": [{"detected_source_language": "IT", "text": "There are items name available."}]}' + headers: + access-control-allow-origin: + - '*' + access-control-expose-headers: + - Server-Timing, X-Trace-ID + content-type: + - application/json + date: + - Mon, 07 Oct 2024 07:54:03 GMT + server-timing: + - l7_lb_tls;dur=77, l7_lb_idle;dur=2, l7_lb_receive;dur=1, l7_lb_total;dur=124 + strict-transport-security: + - max-age=63072000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - Accept-Encoding + x-trace-id: + - 0ec04e3eef784472bedc5be15a1f8259 + status: + code: 200 + message: OK +version: 1 From 38781c142e4237603fcac693628b9c54d76b9f9e Mon Sep 17 00:00:00 2001 From: Halit Celik Date: Mon, 7 Oct 2024 11:53:52 +0200 Subject: [PATCH 5/5] remove venv --- testproject/.venv/lib64 | 1 - testproject/.venv/pyvenv.cfg | 5 ----- 2 files changed, 6 deletions(-) delete mode 120000 testproject/.venv/lib64 delete mode 100644 testproject/.venv/pyvenv.cfg diff --git a/testproject/.venv/lib64 b/testproject/.venv/lib64 deleted file mode 120000 index 7951405..0000000 --- a/testproject/.venv/lib64 +++ /dev/null @@ -1 +0,0 @@ -lib \ No newline at end of file diff --git a/testproject/.venv/pyvenv.cfg b/testproject/.venv/pyvenv.cfg deleted file mode 100644 index 44e63bb..0000000 --- a/testproject/.venv/pyvenv.cfg +++ /dev/null @@ -1,5 +0,0 @@ -home = /home/halit/.pyenv/versions/3.12.3/bin -include-system-site-packages = false -version = 3.12.3 -executable = /home/halit/.pyenv/versions/3.12.3/bin/python3.12 -command = /home/halit/.pyenv/versions/3.12.3/bin/python -m venv /home/halit/Code/django-rosetta/testproject/.venv