From d77ffd86b7ae5c99b68982f5e033a686e504f61e Mon Sep 17 00:00:00 2001 From: Max Inno Date: Mon, 3 Aug 2026 06:50:46 +0300 Subject: [PATCH 1/2] feat(translations): add scope and re-translation options to Apply Pre-Translation --- .../api_resources/translations/enums.py | 12 ++++ .../api_resources/translations/resource.py | 13 ++++ .../tests/test_translations_resources.py | 71 +++++++++++++++++++ 3 files changed, 96 insertions(+) diff --git a/crowdin_api/api_resources/translations/enums.py b/crowdin_api/api_resources/translations/enums.py index e817ba6..06d1eae 100644 --- a/crowdin_api/api_resources/translations/enums.py +++ b/crowdin_api/api_resources/translations/enums.py @@ -14,6 +14,18 @@ class PreTranslationAutoApproveOption(Enum): NONE = "none" +class PreTranslationScope(Enum): + UNTRANSLATED = "untranslated" + TRANSLATED = "translated" + ALL = "all" + + +class PreTranslationReplaceTranslationsOption(Enum): + NONE = "none" + AUTO_TRANSLATED = "autoTranslated" + ALL = "all" + + class CharTransformation(Enum): ASIAN = "asian" EUROPEAN = "european" diff --git a/crowdin_api/api_resources/translations/resource.py b/crowdin_api/api_resources/translations/resource.py index a81fa1c..8ba1e92 100644 --- a/crowdin_api/api_resources/translations/resource.py +++ b/crowdin_api/api_resources/translations/resource.py @@ -11,6 +11,8 @@ CharTransformation, PreTranslationApplyMethod, PreTranslationAutoApproveOption, + PreTranslationReplaceTranslationsOption, + PreTranslationScope, ) @@ -87,6 +89,10 @@ def apply_pre_translation( duplicateTranslations: Optional[bool] = None, skipApprovedTranslations: Optional[bool] = None, translateUntranslatedOnly: Optional[bool] = None, + scope: Optional[PreTranslationScope] = None, + translationModifiedBefore: Optional[str] = None, + replaceTranslationsOption: Optional[PreTranslationReplaceTranslationsOption] = None, + resetApprovalStatus: Optional[bool] = None, translateWithPerfectMatchOnly: Optional[bool] = None, fallbackLanguages: Optional[Iterable[FallbackLanguages]] = None, labelIds: Optional[Iterable[int]] = None, @@ -96,6 +102,9 @@ def apply_pre_translation( """ Apply Pre-Translation. + `translateUntranslatedOnly` is deprecated in favor of `scope` and cannot be + combined with it in the same request. + Link to documentation: https://developer.crowdin.com/api/v2/#operation/api.projects.pre-translations.post """ @@ -126,6 +135,10 @@ def apply_pre_translation( "duplicateTranslations": duplicateTranslations, "skipApprovedTranslations": skipApprovedTranslations, "translateUntranslatedOnly": translateUntranslatedOnly, + "scope": scope, + "translationModifiedBefore": translationModifiedBefore, + "replaceTranslationsOption": replaceTranslationsOption, + "resetApprovalStatus": resetApprovalStatus, "translateWithPerfectMatchOnly": translateWithPerfectMatchOnly, "fallbackLanguages": fallbackLanguages, "labelIds": labelIds, diff --git a/crowdin_api/api_resources/translations/tests/test_translations_resources.py b/crowdin_api/api_resources/translations/tests/test_translations_resources.py index 4b7af7f..21fbfc4 100644 --- a/crowdin_api/api_resources/translations/tests/test_translations_resources.py +++ b/crowdin_api/api_resources/translations/tests/test_translations_resources.py @@ -7,6 +7,8 @@ PreTranslationApplyMethod, PreTranslationAutoApproveOption, PreTranslationEditOperation, + PreTranslationReplaceTranslationsOption, + PreTranslationScope, ) from crowdin_api.api_resources.translations.resource import TranslationsResource from crowdin_api.requester import APIRequester @@ -67,6 +69,10 @@ def test_list_pre_translations(self, m_request, base_absolut_url): "duplicateTranslations": None, "skipApprovedTranslations": None, "translateUntranslatedOnly": None, + "scope": None, + "translationModifiedBefore": None, + "replaceTranslationsOption": None, + "resetApprovalStatus": None, "translateWithPerfectMatchOnly": None, "fallbackLanguages": [], "labelIds": [], @@ -85,6 +91,10 @@ def test_list_pre_translations(self, m_request, base_absolut_url): "duplicateTranslations": False, "skipApprovedTranslations": False, "translateUntranslatedOnly": False, + "scope": PreTranslationScope.TRANSLATED, + "translationModifiedBefore": "2026-01-01T00:00:00+00:00", + "replaceTranslationsOption": PreTranslationReplaceTranslationsOption.AUTO_TRANSLATED, + "resetApprovalStatus": True, "translateWithPerfectMatchOnly": False, "fallbackLanguages": ["lang"], "labelIds": [1], @@ -100,6 +110,10 @@ def test_list_pre_translations(self, m_request, base_absolut_url): "duplicateTranslations": False, "skipApprovedTranslations": False, "translateUntranslatedOnly": False, + "scope": PreTranslationScope.TRANSLATED, + "translationModifiedBefore": "2026-01-01T00:00:00+00:00", + "replaceTranslationsOption": PreTranslationReplaceTranslationsOption.AUTO_TRANSLATED, + "resetApprovalStatus": True, "translateWithPerfectMatchOnly": False, "fallbackLanguages": ["lang"], "labelIds": [1], @@ -147,6 +161,10 @@ def test_apply_pre_translation_with_branchids(self, m_request, base_absolut_url) "duplicateTranslations": None, "skipApprovedTranslations": None, "translateUntranslatedOnly": None, + "scope": None, + "translationModifiedBefore": None, + "replaceTranslationsOption": None, + "resetApprovalStatus": None, "translateWithPerfectMatchOnly": None, "fallbackLanguages": [], "labelIds": [], @@ -186,6 +204,10 @@ def test_apply_pre_translation_edge_cases(self, m_request, base_absolut_url): "duplicateTranslations": None, "skipApprovedTranslations": None, "translateUntranslatedOnly": None, + "scope": None, + "translationModifiedBefore": None, + "replaceTranslationsOption": None, + "resetApprovalStatus": None, "translateWithPerfectMatchOnly": None, "fallbackLanguages": [], "labelIds": [], @@ -237,6 +259,10 @@ def test_apply_pre_translation_with_all_optional_params(self, m_request, base_ab "duplicateTranslations": True, "skipApprovedTranslations": False, "translateUntranslatedOnly": True, + "scope": None, + "translationModifiedBefore": None, + "replaceTranslationsOption": None, + "resetApprovalStatus": None, "translateWithPerfectMatchOnly": False, "fallbackLanguages": [{"languageId": "fr", "userId": 789}], "labelIds": [1, 2, 3], @@ -246,6 +272,51 @@ def test_apply_pre_translation_with_all_optional_params(self, m_request, base_ab path="projects/1/pre-translations", ) + @mock.patch("crowdin_api.requester.APIRequester.request") + def test_apply_pre_translation_with_scope_and_retranslation_options( + self, m_request, base_absolut_url + ): + """Test that scope, translationModifiedBefore, replaceTranslationsOption + and resetApprovalStatus are properly handled.""" + m_request.return_value = "response" + + resource = self.get_resource(base_absolut_url) + response = resource.apply_pre_translation( + projectId=1, + languageIds=["en"], + fileIds=[1], + scope=PreTranslationScope.ALL, + translationModifiedBefore="2026-01-01T00:00:00+00:00", + replaceTranslationsOption=PreTranslationReplaceTranslationsOption.ALL, + resetApprovalStatus=True, + ) + + assert response == "response" + m_request.assert_called_once_with( + method="post", + request_data={ + "languageIds": ["en"], + "fileIds": [1], + "method": None, + "engineId": None, + "aiPromptId": None, + "autoApproveOption": None, + "duplicateTranslations": None, + "skipApprovedTranslations": None, + "translateUntranslatedOnly": None, + "scope": PreTranslationScope.ALL, + "translationModifiedBefore": "2026-01-01T00:00:00+00:00", + "replaceTranslationsOption": PreTranslationReplaceTranslationsOption.ALL, + "resetApprovalStatus": True, + "translateWithPerfectMatchOnly": None, + "fallbackLanguages": [], + "labelIds": [], + "excludeLabelIds": [], + "branchIds": [], + }, + path="projects/1/pre-translations", + ) + @mock.patch("crowdin_api.requester.APIRequester.request") def test_pre_translation_report(self, m_request, base_absolut_url): m_request.return_value = "response" From d49fc434f0c940377965c92fa4c684bc788c2376 Mon Sep 17 00:00:00 2001 From: Max Inno Date: Fri, 14 Aug 2026 17:02:51 +0300 Subject: [PATCH 2/2] fix: Copilot suggestions --- .../api_resources/translations/resource.py | 6 +++++ .../tests/test_translations_resources.py | 22 +++++++++++++++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/crowdin_api/api_resources/translations/resource.py b/crowdin_api/api_resources/translations/resource.py index 8ba1e92..2c5b29c 100644 --- a/crowdin_api/api_resources/translations/resource.py +++ b/crowdin_api/api_resources/translations/resource.py @@ -108,6 +108,12 @@ def apply_pre_translation( Link to documentation: https://developer.crowdin.com/api/v2/#operation/api.projects.pre-translations.post """ + if translateUntranslatedOnly is not None and scope is not None: + raise ValueError( + "translateUntranslatedOnly is deprecated in favor of scope and " + "cannot be combined with it in the same request." + ) + if fallbackLanguages is None: fallbackLanguages = [] diff --git a/crowdin_api/api_resources/translations/tests/test_translations_resources.py b/crowdin_api/api_resources/translations/tests/test_translations_resources.py index 21fbfc4..176fcf1 100644 --- a/crowdin_api/api_resources/translations/tests/test_translations_resources.py +++ b/crowdin_api/api_resources/translations/tests/test_translations_resources.py @@ -90,7 +90,6 @@ def test_list_pre_translations(self, m_request, base_absolut_url): "autoApproveOption": PreTranslationAutoApproveOption.ALL, "duplicateTranslations": False, "skipApprovedTranslations": False, - "translateUntranslatedOnly": False, "scope": PreTranslationScope.TRANSLATED, "translationModifiedBefore": "2026-01-01T00:00:00+00:00", "replaceTranslationsOption": PreTranslationReplaceTranslationsOption.AUTO_TRANSLATED, @@ -109,7 +108,7 @@ def test_list_pre_translations(self, m_request, base_absolut_url): "autoApproveOption": PreTranslationAutoApproveOption.ALL, "duplicateTranslations": False, "skipApprovedTranslations": False, - "translateUntranslatedOnly": False, + "translateUntranslatedOnly": None, "scope": PreTranslationScope.TRANSLATED, "translationModifiedBefore": "2026-01-01T00:00:00+00:00", "replaceTranslationsOption": PreTranslationReplaceTranslationsOption.AUTO_TRANSLATED, @@ -317,6 +316,25 @@ def test_apply_pre_translation_with_scope_and_retranslation_options( path="projects/1/pre-translations", ) + @mock.patch("crowdin_api.requester.APIRequester.request") + def test_apply_pre_translation_with_scope_and_translate_untranslated_only_raises( + self, m_request, base_absolut_url + ): + """Test that combining the deprecated translateUntranslatedOnly with scope + raises a ValueError instead of being sent to the API.""" + resource = self.get_resource(base_absolut_url) + + with pytest.raises(ValueError): + resource.apply_pre_translation( + projectId=1, + languageIds=["en"], + fileIds=[1], + translateUntranslatedOnly=True, + scope=PreTranslationScope.ALL, + ) + + m_request.assert_not_called() + @mock.patch("crowdin_api.requester.APIRequester.request") def test_pre_translation_report(self, m_request, base_absolut_url): m_request.return_value = "response"