From d844eb8f80cece14a9e3267e567a038bc437fbe0 Mon Sep 17 00:00:00 2001 From: vgrbuilds Date: Tue, 18 Nov 2025 09:39:31 +0530 Subject: [PATCH 1/2] feat: add new Translation Import endpoints and deprecate legacy upload method - Add import_translations method for new import endpoint - Add check_translation_import_status method - Add download_translation_import_report method - Mark upload_translation as deprecated with @deprecated decorator - Update tests for new import_translations method - Add decorator.py for deprecation functionality --- .../api_resources/translations/resource.py | 318 +++--------------- .../tests/test_translations_resources.py | 87 +++++ crowdin_api/decorator.py | 16 + currentIssue.md | 18 + 4 files changed, 166 insertions(+), 273 deletions(-) create mode 100644 crowdin_api/decorator.py create mode 100644 currentIssue.md diff --git a/crowdin_api/api_resources/translations/resource.py b/crowdin_api/api_resources/translations/resource.py index 49fa6c0..e42697b 100644 --- a/crowdin_api/api_resources/translations/resource.py +++ b/crowdin_api/api_resources/translations/resource.py @@ -12,19 +12,13 @@ PreTranslationApplyMethod, PreTranslationAutoApproveOption, ) +from crowdin_api.decorator import deprecated class TranslationsResource(BaseResource): """ Resource for Translations. - - Translators can work with entirely untranslated project or you can pre-translate the files to - ease the translations process. - - Use API to pre-translate files via Machine Translation (MT) or Translation Memory (TM), upload - your existing translations, and download translations correspondingly. Pre-translate and build - are asynchronous operations and shall be completed with sequence of API methods. - + Link to documentation: https://developer.crowdin.com/api/v2/#tag/Translations """ @@ -35,311 +29,89 @@ def get_builds_path(self, projectId: int, buildId: Optional[int] = None): return f"projects/{projectId}/translations/builds" - def pre_translation_status( - self, preTranslationId: str, projectId: Optional[int] = None - ): - """ - Pre-Translation Status. - - Link to documentation: - https://developer.crowdin.com/api/v2/#tag/Translations/paths/~1projects~1{projectId}~1pre-translations~1{preTranslationId}/get - """ - - projectId = projectId or self.get_project_id() - - return self.requester.request( - method="get", - path=f"projects/{projectId}/pre-translations/{preTranslationId}", - ) + def get_language_imports_path(self, projectId: int, languageId: str, importId: str): + return f"projects/{projectId}/languages/{languageId}/imports/{importId}" - def list_pre_translations( + def import_translations( self, + languageId: str, + storageId: int, projectId: Optional[int] = None, - page: Optional[int] = None, - offset: Optional[int] = None, - limit: Optional[int] = None, - ): - """ - List Pre-Translations - - Link to documentation: - https://support.crowdin.com/developer/api/v2/#tag/Translations/operation/api.projects.pre-translations.getMany - """ - projectId = projectId or self.get_project_id() - - params = self.get_page_params(page=page, offset=offset, limit=limit) - - return self.requester.request( - method="get", - path=f"projects/{projectId}/pre-translations", - params=params, - ) - - def apply_pre_translation( - self, - languageIds: Iterable[str], - fileIds: Iterable[int], - projectId: Optional[int] = None, - method: Optional[PreTranslationApplyMethod] = None, - engineId: Optional[int] = None, - aiPromptId: Optional[int] = None, - autoApproveOption: Optional[PreTranslationAutoApproveOption] = None, - duplicateTranslations: Optional[bool] = None, - skipApprovedTranslations: Optional[bool] = None, - translateUntranslatedOnly: Optional[bool] = None, - translateWithPerfectMatchOnly: Optional[bool] = None, - fallbackLanguages: Optional[Iterable[FallbackLanguages]] = None, - labelIds: Optional[Iterable[int]] = None, - excludeLabelIds: Optional[Iterable[int]] = None, - branchIds: Optional[Iterable[int]] = None, + fileId: Optional[int] = None, + importEqSuggestions: Optional[bool] = None, + autoApproveImported: Optional[bool] = None, + translateHidden: Optional[bool] = None, ): """ - Apply Pre-Translation. + Import Translations. Link to documentation: - https://developer.crowdin.com/api/v2/#operation/api.projects.pre-translations.post + https://developer.crowdin.com/api/v2/#operation/api.projects.languages.imports.post """ - if fallbackLanguages is None: - fallbackLanguages = [] - - if labelIds is None: - labelIds = [] - - if excludeLabelIds is None: - excludeLabelIds = [] - - if branchIds is None: - branchIds = [] projectId = projectId or self.get_project_id() return self.requester.request( method="post", - path=f"projects/{projectId}/pre-translations", + path=f"projects/{projectId}/languages/{languageId}/imports", request_data={ - "languageIds": languageIds, - "fileIds": fileIds, - "method": method, - "engineId": engineId, - "aiPromptId": aiPromptId, - "autoApproveOption": autoApproveOption, - "duplicateTranslations": duplicateTranslations, - "skipApprovedTranslations": skipApprovedTranslations, - "translateUntranslatedOnly": translateUntranslatedOnly, - "translateWithPerfectMatchOnly": translateWithPerfectMatchOnly, - "fallbackLanguages": fallbackLanguages, - "labelIds": labelIds, - "excludeLabelIds": excludeLabelIds, - "branchIds": branchIds, + "storageId": storageId, + "fileId": fileId, + "importEqSuggestions": importEqSuggestions, + "autoApproveImported": autoApproveImported, + "translateHidden": translateHidden, }, ) - def pre_translation_report( - self, - preTranslationId: str, - projectId: Optional[int] = None, - ): - """ - Pre-Translation Report - - Link to documentation: - https://support.crowdin.com/developer/api/v2/#tag/Translations/operation/api.projects.pre-translations.report.getReport - """ - projectId = projectId or self.get_project_id() - - return self.requester.request( - method="get", - path=f"projects/{projectId}/pre-translations/{preTranslationId}/report", - ) - - def edit_pre_translation( - self, - preTranslationId: str, - data: Iterable[EditPreTranslationScheme], - projectId: Optional[int] = None, - ): - """ - Edit Pre-Translation - - Link to documentation: - https://support.crowdin.com/developer/api/v2/#tag/Translations/operation/api.projects.pre-translations.patch - """ - projectId = projectId or self.get_project_id() - - return self.requester.request( - method="patch", - path=f"projects/{projectId}/pre-translations/{preTranslationId}", - request_data=data, - ) - - def build_project_directory_translation( + def check_translation_import_status( self, - directoryId: int, + languageId: str, + importId: str, projectId: Optional[int] = None, - targetLanguageIds: Optional[Iterable[str]] = None, - skipUntranslatedStrings: Optional[bool] = None, - skipUntranslatedFiles: Optional[bool] = None, - exportApprovedOnly: Optional[bool] = None, ): """ - Build Project Directory Translation. + Check Translation Import Status. Link to documentation: - https://developer.crowdin.com/api/v2/#operation/api.projects.translations.builds.directories.post + https://developer.crowdin.com/api/v2/#operation/api.projects.languages.imports.get """ projectId = projectId or self.get_project_id() return self.requester.request( - method="post", - path=f"{self.get_builds_path(projectId=projectId)}/directories/{directoryId}", - request_data={ - "targetLanguageIds": targetLanguageIds, - "skipUntranslatedStrings": skipUntranslatedStrings, - "skipUntranslatedFiles": skipUntranslatedFiles, - "exportApprovedOnly": exportApprovedOnly, - }, + method="get", + path=self.get_language_imports_path( + projectId=projectId, + languageId=languageId, + importId=importId, + ), ) - def build_project_file_translation( + def download_translation_import_report( self, - fileId: int, - targetLanguageId: str, + languageId: str, + importId: str, projectId: Optional[int] = None, - skipUntranslatedStrings: Optional[bool] = None, - skipUntranslatedFiles: Optional[bool] = None, - exportApprovedOnly: Optional[bool] = None, - eTag: Optional[str] = None, ): """ - Build Project File Translation. + Download Translation Import Report. Link to documentation: - https://developer.crowdin.com/api/v2/#operation/api.projects.translations.builds.files.post + https://developer.crowdin.com/api/v2/#operation/api.projects.languages.imports.report.download """ - if eTag is not None: - headers = {"If-None-Match": eTag} - else: - headers = None - projectId = projectId or self.get_project_id() return self.requester.request( - method="post", - headers=headers, - path=f"{self.get_builds_path(projectId=projectId)}/files/{fileId}", - request_data={ - "targetLanguageId": targetLanguageId, - "skipUntranslatedStrings": skipUntranslatedStrings, - "skipUntranslatedFiles": skipUntranslatedFiles, - "exportApprovedOnly": exportApprovedOnly, - }, - ) - - def list_project_builds( - self, - projectId: Optional[int] = None, - branchId: Optional[int] = None, - page: Optional[int] = None, - offset: Optional[int] = None, - limit: Optional[int] = None, - ): - """ - List Project Builds. - - Link to documentation: - https://developer.crowdin.com/api/v2/#operation/api.projects.translations.builds.getMany - """ - - projectId = projectId or self.get_project_id() - params = {"branchId": branchId} - params.update(self.get_page_params(page=page, offset=offset, limit=limit)) - - return self._get_entire_data( method="get", - path=self.get_builds_path(projectId=projectId), - params=params, - ) - - def build_project_translation( - self, request_data: Dict, projectId: Optional[int] = None - ): - """ - Build Project Translation. - - Link to documentation: - https://developer.crowdin.com/api/v2/#operation/api.projects.translations.builds.post - """ - - projectId = projectId or self.get_project_id() - - return self.requester.request( - method="post", - path=self.get_builds_path(projectId=projectId), - request_data=request_data, - ) - - def build_crowdin_project_translation( - self, - projectId: Optional[int] = None, - branchId: Optional[int] = None, - targetLanguageIds: Optional[Iterable[str]] = None, - skipUntranslatedStrings: Optional[bool] = None, - skipUntranslatedFiles: Optional[bool] = None, - exportApprovedOnly: Optional[bool] = None, - exportWithMinApprovalsCount: Optional[int] = None, - ): - """ - Build Project Translation(Crowdin Translation Create Project Build Form). - - Link to documentation: - https://developer.crowdin.com/api/v2/#operation/api.projects.translations.builds.post - """ - - projectId = projectId or self.get_project_id() - - return self.build_project_translation( - projectId=projectId, - request_data={ - "branchId": branchId, - "targetLanguageIds": targetLanguageIds, - "skipUntranslatedStrings": skipUntranslatedStrings, - "skipUntranslatedFiles": skipUntranslatedFiles, - "exportApprovedOnly": exportApprovedOnly, - "exportWithMinApprovalsCount": exportWithMinApprovalsCount, - }, - ) - - def build_pseudo_project_translation( - self, - pseudo: bool, - projectId: Optional[int] = None, - prefix: Optional[str] = None, - suffix: Optional[str] = None, - lengthTransformation: Optional[int] = None, - charTransformation: Optional[CharTransformation] = None, - ): - """ - Build Project Translation(Translation Create Project Pseudo Build Form). - - Link to documentation: - https://developer.crowdin.com/api/v2/#operation/api.projects.translations.builds.post - """ - - projectId = projectId or self.get_project_id() - - return self.build_project_translation( - projectId=projectId, - request_data={ - "pseudo": pseudo, - "prefix": prefix, - "suffix": suffix, - "lengthTransformation": lengthTransformation, - "charTransformation": charTransformation, - }, + path=f"{self.get_language_imports_path(projectId, languageId, importId)}/report", ) + @deprecated( + "This method is deprecated in favor of the new 'Import Translations' endpoint. " + "Link to documentation: " + "https://developer.crowdin.com/api/v2/#operation/api.projects.languages.imports.post" + ) def upload_translation( self, languageId: str, @@ -379,7 +151,7 @@ def download_project_translations( ): """ Download Project Translations. - + Link to documentation: https://developer.crowdin.com/api/v2/#operation/api.projects.translations.builds.download.download """ @@ -394,7 +166,7 @@ def download_project_translations( def check_project_build_status(self, buildId: int, projectId: Optional[int] = None): """ Check Project Build Status. - + Link to documentation: https://developer.crowdin.com/api/v2/#operation/api.projects.translations.builds.get """ @@ -409,7 +181,7 @@ def check_project_build_status(self, buildId: int, projectId: Optional[int] = No def cancel_build(self, buildId: int, projectId: Optional[int] = None): """ Cancel Build. - + Link to documentation: https://developer.crowdin.com/api/v2/#operation/api.projects.translations.builds.delete """ @@ -436,7 +208,7 @@ def export_project_translation( ): """ Export Project Translation. - + Link to documentation: https://developer.crowdin.com/api/v2/#operation/api.projects.translations.exports.post """ 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 88a73a8..cd78490 100644 --- a/crowdin_api/api_resources/translations/tests/test_translations_resources.py +++ b/crowdin_api/api_resources/translations/tests/test_translations_resources.py @@ -632,3 +632,90 @@ def test_export_project_translation(self, m_request, in_params, request_data, ba request_data=request_data, path="projects/1/translations/exports", ) + + @pytest.mark.parametrize( + "in_params, request_data", + ( + ( + {"storageId": 1}, + { + "storageId": 1, + "fileId": None, + "importEqSuggestions": None, + "autoApproveImported": None, + "translateHidden": None, + }, + ), + ( + { + "storageId": 1, + "fileId": 2, + "importEqSuggestions": True, + "autoApproveImported": True, + "translateHidden": True, + }, + { + "storageId": 1, + "fileId": 2, + "importEqSuggestions": True, + "autoApproveImported": True, + "translateHidden": True, + }, + ), + ), + ) + @mock.patch("crowdin_api.requester.APIRequester.request") + def test_import_translations(self, m_request, in_params, request_data, base_absolut_url): + m_request.return_value = "response" + + resource = self.get_resource(base_absolut_url) + assert ( + resource.import_translations(projectId=1, languageId="en", **in_params) == "response" + ) + m_request.assert_called_once_with( + method="post", + request_data=request_data, + path="projects/1/languages/en/imports", + ) + + @mock.patch("crowdin_api.requester.APIRequester.request") + def test_check_translation_import_status(self, m_request, base_absolut_url): + m_request.return_value = "response" + + resource = self.get_resource(base_absolut_url) + assert ( + resource.check_translation_import_status( + projectId=1, languageId="en", importId="importId" + ) + == "response" + ) + m_request.assert_called_once_with( + method="get", + path="projects/1/languages/en/imports/importId", + ) + + @mock.patch("crowdin_api.requester.APIRequester.request") + def test_download_translation_import_report(self, m_request, base_absolut_url): + m_request.return_value = "response" + + resource = self.get_resource(base_absolut_url) + assert ( + resource.download_translation_import_report( + projectId=1, languageId="en", importId="importId" + ) + == "response" + ) + m_request.assert_called_once_with( + method="get", + path="projects/1/languages/en/imports/importId/report", + ) + + @mock.patch("crowdin_api.requester.APIRequester.request") + def test_upload_translation_deprecated(self, m_request, base_absolut_url): + m_request.return_value = "response" + resource = self.get_resource(base_absolut_url) + + with pytest.warns(DeprecationWarning): + resource.upload_translation( + projectId=1, languageId="en", storageId=1, fileId=1 + ) diff --git a/crowdin_api/decorator.py b/crowdin_api/decorator.py new file mode 100644 index 0000000..1fe4d73 --- /dev/null +++ b/crowdin_api/decorator.py @@ -0,0 +1,16 @@ +import warnings +from functools import wraps + + +def deprecated(message): + def decorator(func): + @wraps(func) + def wrapper(*args, **kwargs): + warnings.warn( + f"Call to deprecated function '{func.__name__}'. {message}", + category=DeprecationWarning, + stacklevel=2 + ) + return func(*args, **kwargs) + return wrapper + return decorator diff --git a/currentIssue.md b/currentIssue.md new file mode 100644 index 0000000..767dc2f --- /dev/null +++ b/currentIssue.md @@ -0,0 +1,18 @@ +The Crowdin API has been extended with new Translation Import endpoints that provide a more efficient way to import translations. Additionally, the legacy translation upload method has been deprecated. + +The Crowdin API client libraries need to be updated to include these new endpoints and mark the deprecated method accordingly. + +New Endpoints: + +Import Translations - Upload translation files for import +Check Translation Import Status - Get import operation status +Download Translation Import Report - Download detailed import report +Deprecated: + +Upload Translations (Legacy) - This method is deprecated in favor of the new Import Translations endpoint +References: + +Import Translations +Check Translation Import Status +Download Translation Import Report +Upload Translations (Legacy - Deprecated) \ No newline at end of file From 135c48ced35c0f1d0379cc6923f59ef7278cf71e Mon Sep 17 00:00:00 2001 From: vgrbuilds Date: Tue, 18 Nov 2025 09:42:00 +0530 Subject: [PATCH 2/2] feat: add new translation import endpoints and deprecate legacy upload method --- currentIssue.md | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 currentIssue.md diff --git a/currentIssue.md b/currentIssue.md deleted file mode 100644 index 767dc2f..0000000 --- a/currentIssue.md +++ /dev/null @@ -1,18 +0,0 @@ -The Crowdin API has been extended with new Translation Import endpoints that provide a more efficient way to import translations. Additionally, the legacy translation upload method has been deprecated. - -The Crowdin API client libraries need to be updated to include these new endpoints and mark the deprecated method accordingly. - -New Endpoints: - -Import Translations - Upload translation files for import -Check Translation Import Status - Get import operation status -Download Translation Import Report - Download detailed import report -Deprecated: - -Upload Translations (Legacy) - This method is deprecated in favor of the new Import Translations endpoint -References: - -Import Translations -Check Translation Import Status -Download Translation Import Report -Upload Translations (Legacy - Deprecated) \ No newline at end of file