From 7784b94ef804a49c217250906310af2b58f46047 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 13 Nov 2025 15:17:16 +0000 Subject: [PATCH 1/3] Initial plan From 21c204aa9cee76df6096b0fa55021858c86f8b0e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 13 Nov 2025 15:28:50 +0000 Subject: [PATCH 2/3] Add batch operation endpoints for String Comments, Approvals, and Translations Co-authored-by: andrii-bodnar <29282228+andrii-bodnar@users.noreply.github.com> --- src/CrowdinApiClient/Api/StringCommentApi.php | 18 +++++ .../Api/StringTranslationApi.php | 36 ++++++++++ .../Api/StringCommentApiTest.php | 63 ++++++++++++++++ .../Api/StringTranslationApiTest.php | 72 +++++++++++++++++++ 4 files changed, 189 insertions(+) diff --git a/src/CrowdinApiClient/Api/StringCommentApi.php b/src/CrowdinApiClient/Api/StringCommentApi.php index d8afbcb4..f29d86e0 100644 --- a/src/CrowdinApiClient/Api/StringCommentApi.php +++ b/src/CrowdinApiClient/Api/StringCommentApi.php @@ -86,4 +86,22 @@ public function delete(int $projectId, int $stringCommentId) $path = sprintf('projects/%d/comments/%d', $projectId, $stringCommentId); return $this->_delete($path); } + + /** + * String Comment Batch Operations + * @link https://developer.crowdin.com/api/v2/#operation/api.projects.comments.batchPatch API Documentation + * @link https://developer.crowdin.com/enterprise/api/v2/#operation/api.projects.comments.batchPatch API Documentation Enterprise + * + * @param int $projectId + * @param array $data + * string $data[op] required Patch operation to perform (replace, test)
+ * string $data[path] required A JSON Pointer as defined in RFC 6901
+ * value $data[value] required The value to be used within the operation (string, int, bool, or object) + * @return mixed + */ + public function batchOperations(int $projectId, array $data) + { + $path = sprintf('projects/%d/comments', $projectId); + return $this->_patch($path, StringComment::class, $data); + } } diff --git a/src/CrowdinApiClient/Api/StringTranslationApi.php b/src/CrowdinApiClient/Api/StringTranslationApi.php index 8c663981..3344d7a1 100644 --- a/src/CrowdinApiClient/Api/StringTranslationApi.php +++ b/src/CrowdinApiClient/Api/StringTranslationApi.php @@ -284,4 +284,40 @@ public function deleteVote(int $projectId, int $voteId) $path = sprintf('projects/%d/votes/%d', $projectId, $voteId); return $this->_delete($path); } + + /** + * Approval Batch Operations + * @link https://developer.crowdin.com/api/v2/#operation/api.projects.approvals.patch API Documentation + * @link https://developer.crowdin.com/enterprise/api/v2/#operation/api.projects.approvals.patch API Documentation Enterprise + * + * @param int $projectId + * @param array $data + * string $data[op] required Patch operation to perform (replace, test)
+ * string $data[path] required A JSON Pointer as defined in RFC 6901
+ * value $data[value] required The value to be used within the operation (string, int, bool, or object) + * @return mixed + */ + public function batchOperationsForApprovals(int $projectId, array $data) + { + $path = sprintf('projects/%d/approvals', $projectId); + return $this->_patch($path, StringTranslationApproval::class, $data); + } + + /** + * Translation Batch Operations + * @link https://developer.crowdin.com/api/v2/#operation/api.projects.translations.patch API Documentation + * @link https://developer.crowdin.com/enterprise/api/v2/#operation/api.projects.translations.patch API Documentation Enterprise + * + * @param int $projectId + * @param array $data + * string $data[op] required Patch operation to perform (replace, test)
+ * string $data[path] required A JSON Pointer as defined in RFC 6901
+ * value $data[value] required The value to be used within the operation (string, int, bool, or object) + * @return mixed + */ + public function batchOperationsForTranslations(int $projectId, array $data) + { + $path = sprintf('projects/%d/translations', $projectId); + return $this->_patch($path, StringTranslation::class, $data); + } } diff --git a/tests/CrowdinApiClient/Api/StringCommentApiTest.php b/tests/CrowdinApiClient/Api/StringCommentApiTest.php index 34ec1dc0..475ea0e5 100644 --- a/tests/CrowdinApiClient/Api/StringCommentApiTest.php +++ b/tests/CrowdinApiClient/Api/StringCommentApiTest.php @@ -218,4 +218,67 @@ public function testDelete() $this->mockRequestDelete('/projects/1/comments/1'); $this->crowdin->stringComment->delete(1, 1); } + + public function testBatchOperations() + { + $this->mockRequest([ + 'path' => '/projects/1/comments', + 'method' => 'patch', + 'response' => '{ + "data": [ + { + "data": { + "id": 2, + "text": "Updated comment text", + "userId": 6, + "stringId": 742, + "user": { + "id": 12, + "username": "john_smith", + "fullName": "John Smith", + "avatarUrl": "" + }, + "string": { + "id": 123, + "text": "HTML page example", + "type": "text", + "hasPlurals": false, + "isIcu": false, + "context": "Document Title\\r\\nXPath: /html/head/title", + "fileId": 22 + }, + "languageId": "bg", + "type": "issue", + "issueType": "source_mistake", + "issueStatus": "resolved", + "resolverId": 6, + "resolver": { + "id": 12, + "username": "john_smith", + "fullName": "John Smith", + "avatarUrl": "" + }, + "resolvedAt": "2019-09-20T11:05:24+00:00", + "createdAt": "2019-09-20T11:05:24+00:00" + } + } + ] + }' + ]); + + $batchResult = $this->crowdin->stringComment->batchOperations(1, [ + [ + 'op' => 'replace', + 'path' => '/2/text', + 'value' => 'Updated comment text' + ], + [ + 'op' => 'replace', + 'path' => '/2/issueStatus', + 'value' => 'resolved' + ] + ]); + + $this->assertInstanceOf(StringComment::class, $batchResult); + } } diff --git a/tests/CrowdinApiClient/Api/StringTranslationApiTest.php b/tests/CrowdinApiClient/Api/StringTranslationApiTest.php index 9bfdebab..8aa92130 100644 --- a/tests/CrowdinApiClient/Api/StringTranslationApiTest.php +++ b/tests/CrowdinApiClient/Api/StringTranslationApiTest.php @@ -218,4 +218,76 @@ public function testListLanguageTranslations(): void $this->assertEquals(7815, $stringTranslations[1]->getStringId()); $this->assertEquals(9011, $stringTranslations[2]->getStringId()); } + + public function testBatchOperationsForApprovals(): void + { + $this->mockRequest([ + 'path' => '/projects/2/approvals', + 'method' => 'patch', + 'response' => json_encode([ + 'data' => [ + [ + 'data' => [ + 'id' => 12, + 'user' => [ + 'id' => 12, + 'username' => 'john_smith', + 'fullName' => 'John Smith', + 'userType' => 'translator' + ], + 'translationId' => 190695, + 'stringId' => 35434, + 'languageId' => 'uk', + 'workflowStepId' => 10, + 'createdAt' => '2019-09-20T11:05:24+00:00' + ] + ] + ] + ]) + ]); + + $batchResult = $this->crowdin->stringTranslation->batchOperationsForApprovals(2, [ + [ + 'op' => 'replace', + 'path' => '/12/workflowStepId', + 'value' => 10 + ] + ]); + + $this->assertInstanceOf(\CrowdinApiClient\Model\StringTranslationApproval::class, $batchResult); + } + + public function testBatchOperationsForTranslations(): void + { + $this->mockRequest([ + 'path' => '/projects/2/translations', + 'method' => 'patch', + 'response' => json_encode([ + 'data' => [ + [ + 'data' => [ + 'id' => 190695, + 'text' => 'Updated translation text', + 'pluralCategoryName' => 'few', + 'user' => [ + 'id' => 19, + 'login' => 'john_doe', + ], + 'rating' => 10, + ] + ] + ] + ]) + ]); + + $batchResult = $this->crowdin->stringTranslation->batchOperationsForTranslations(2, [ + [ + 'op' => 'replace', + 'path' => '/190695/text', + 'value' => 'Updated translation text' + ] + ]); + + $this->assertInstanceOf(StringTranslation::class, $batchResult); + } } From ea3230570b289fcf617c9af1d576832b03e8fede Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 13 Nov 2025 15:42:08 +0000 Subject: [PATCH 3/3] Rename batch operation methods for better naming consistency Co-authored-by: andrii-bodnar <29282228+andrii-bodnar@users.noreply.github.com> --- src/CrowdinApiClient/Api/StringTranslationApi.php | 4 ++-- tests/CrowdinApiClient/Api/StringTranslationApiTest.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/CrowdinApiClient/Api/StringTranslationApi.php b/src/CrowdinApiClient/Api/StringTranslationApi.php index 3344d7a1..ace80f6f 100644 --- a/src/CrowdinApiClient/Api/StringTranslationApi.php +++ b/src/CrowdinApiClient/Api/StringTranslationApi.php @@ -297,7 +297,7 @@ public function deleteVote(int $projectId, int $voteId) * value $data[value] required The value to be used within the operation (string, int, bool, or object) * @return mixed */ - public function batchOperationsForApprovals(int $projectId, array $data) + public function approvalsBatchOperations(int $projectId, array $data) { $path = sprintf('projects/%d/approvals', $projectId); return $this->_patch($path, StringTranslationApproval::class, $data); @@ -315,7 +315,7 @@ public function batchOperationsForApprovals(int $projectId, array $data) * value $data[value] required The value to be used within the operation (string, int, bool, or object) * @return mixed */ - public function batchOperationsForTranslations(int $projectId, array $data) + public function translationsBatchOperations(int $projectId, array $data) { $path = sprintf('projects/%d/translations', $projectId); return $this->_patch($path, StringTranslation::class, $data); diff --git a/tests/CrowdinApiClient/Api/StringTranslationApiTest.php b/tests/CrowdinApiClient/Api/StringTranslationApiTest.php index 8aa92130..9456340f 100644 --- a/tests/CrowdinApiClient/Api/StringTranslationApiTest.php +++ b/tests/CrowdinApiClient/Api/StringTranslationApiTest.php @@ -246,7 +246,7 @@ public function testBatchOperationsForApprovals(): void ]) ]); - $batchResult = $this->crowdin->stringTranslation->batchOperationsForApprovals(2, [ + $batchResult = $this->crowdin->stringTranslation->approvalsBatchOperations(2, [ [ 'op' => 'replace', 'path' => '/12/workflowStepId', @@ -280,7 +280,7 @@ public function testBatchOperationsForTranslations(): void ]) ]); - $batchResult = $this->crowdin->stringTranslation->batchOperationsForTranslations(2, [ + $batchResult = $this->crowdin->stringTranslation->translationsBatchOperations(2, [ [ 'op' => 'replace', 'path' => '/190695/text',