diff --git a/docs/how-to/integrations/machine-translation.md b/docs/how-to/integrations/machine-translation.md index 653bba0d..93976430 100644 --- a/docs/how-to/integrations/machine-translation.md +++ b/docs/how-to/integrations/machine-translation.md @@ -127,6 +127,13 @@ WAGTAILLOCALIZE_MACHINE_TRANSLATOR = { # Optional DeepL API setting. Accepts "default", "prefer_more" or "prefer_less".\ # For more information see the API docs https://www.deepl.com/docs-api/translate-text/ "FORMALITY": "", + # Optional DeepL Glossary IDs by (source, target) language pairs + "GLOSSARY_IDS": { + ("EN", "ES"): "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + ("EN", "FR"): "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + }, + # Optional timeout for the request to the DeepL API + "TIMEOUT": 30, }, } ``` diff --git a/wagtail_localize/machine_translators/deepl.py b/wagtail_localize/machine_translators/deepl.py index ba29318a..1f4dfc18 100644 --- a/wagtail_localize/machine_translators/deepl.py +++ b/wagtail_localize/machine_translators/deepl.py @@ -28,27 +28,43 @@ def get_api_endpoint(self): return "https://api-free.deepl.com/v2/translate" return "https://api.deepl.com/v2/translate" - def translate(self, source_locale, target_locale, strings): + def get_parameters(self, source_locale, target_locale, strings): + source_lang = language_code(source_locale.language_code) + target_lang = language_code(target_locale.language_code, is_target=True) + parameters = { "auth_key": self.options["AUTH_KEY"], "text": [string.data for string in strings], "tag_handling": "xml", - "source_lang": language_code(source_locale.language_code), - "target_lang": language_code(target_locale.language_code, is_target=True), + "source_lang": source_lang, + "target_lang": target_lang, } - if self.options.get("FORMALITY"): - if self.options["FORMALITY"] in SUPPORTED_FORMALITY_OPTIONS: - parameters["formality"] = self.options["FORMALITY"] + if formality := self.options.get("FORMALITY"): + if formality in SUPPORTED_FORMALITY_OPTIONS: + parameters["formality"] = formality + else: + warnings.warn( + f"Unsupported formality option '{formality}'. " + f"Supported options are: {', '.join(SUPPORTED_FORMALITY_OPTIONS)}" + ) + + if glossaries := self.options.get("GLOSSARY_IDS"): + lang_pair = (source_lang, target_lang) + if glossary_id := glossaries.get(lang_pair): + parameters["glossary_id"] = glossary_id else: warnings.warn( - f"Unsupported formality option '{self.options['FORMALITY']}'. Supported options are: {', '.join(SUPPORTED_FORMALITY_OPTIONS)}" + f"No glossary defined for (source, target) pair: {lang_pair}" ) + return parameters + + def translate(self, source_locale, target_locale, strings): response = requests.post( self.get_api_endpoint(), - parameters, - timeout=30, + self.get_parameters(source_locale, target_locale, strings), + timeout=int(self.options.get("TIMEOUT", 30)), ) return { diff --git a/wagtail_localize/machine_translators/tests/test_deepl_translator.py b/wagtail_localize/machine_translators/tests/test_deepl_translator.py index e9570314..bbd46dce 100644 --- a/wagtail_localize/machine_translators/tests/test_deepl_translator.py +++ b/wagtail_localize/machine_translators/tests/test_deepl_translator.py @@ -33,6 +33,28 @@ }, } +DEEPL_SETTINGS_WITH_GLOSSARY_IDS = { + "CLASS": "wagtail_localize.machine_translators.deepl.DeepLTranslator", + "OPTIONS": { + "AUTH_KEY": "asd-23-ssd-243-adsf-dummy-auth-key:bla", + "GLOSSARY_IDS": { + ("EN", "DE"): "test-id-de", + ("EN", "FR"): "test-id-fr", + }, + }, +} + +DEEPL_SETTINGS_WITH_MISSING_GLOSSARY_IDS = { + "CLASS": "wagtail_localize.machine_translators.deepl.DeepLTranslator", + "OPTIONS": { + "AUTH_KEY": "asd-23-ssd-243-adsf-dummy-auth-key:bla", + "GLOSSARY_IDS": { + ("EN", "FR"): "test-id-fr", + ("EN", "DE"): "test-id-de", + }, + }, +} + class TestDeeplTranslator(TestCase): @override_settings(WAGTAILLOCALIZE_MACHINE_TRANSLATOR=DEEPL_SETTINGS_FREE_ENDPOINT) @@ -120,3 +142,36 @@ def test_language_code_as_target(self): for code, expected_value in mapping.items(): with self.subTest(f"Testing language_code with {code} as target"): self.assertEqual(expected_value, language_code(code, is_target=True)) + + @override_settings( + WAGTAILLOCALIZE_MACHINE_TRANSLATOR=DEEPL_SETTINGS_WITH_GLOSSARY_IDS + ) + @patch("requests.post") + def test_translate_with_glossary_ids(self, mock_post): + translator = get_machine_translator() + source_locale = Mock(language_code="en") + target_locale = Mock(language_code="de") + strings = [StringValue("Test string")] + + translator.translate(source_locale, target_locale, strings) + + mock_post.assert_called_once() + called_args, called_kwargs = mock_post.call_args + self.assertIn("glossary_id", called_args[1]) + self.assertEqual(called_args[1]["glossary_id"], "test-id-de") + + @override_settings( + WAGTAILLOCALIZE_MACHINE_TRANSLATOR=DEEPL_SETTINGS_WITH_MISSING_GLOSSARY_IDS + ) + @patch("requests.post") + def test_translate_with_missing_glossary_ids(self, mock_post): + translator = get_machine_translator() + source_locale = Mock(language_code="en") + target_locale = Mock(language_code="es") + strings = [StringValue("Test string")] + + translator.translate(source_locale, target_locale, strings) + + mock_post.assert_called_once() + called_args, called_kwargs = mock_post.call_args + self.assertNotIn("glossary_id", called_args[1])