From 79980ce1a8e68c11b5b50697681a8883dbb8199f Mon Sep 17 00:00:00 2001 From: GabrielBBaldez <130607246+GabrielBBaldez@users.noreply.github.com> Date: Mon, 8 Jun 2026 10:16:37 -0300 Subject: [PATCH 1/2] feat: add updateOption parameter to string editing endpoints Expose the updateOption query parameter on editSourceString and stringBatchOperations so consumers can control whether existing translations and approvals are kept when a source string is edited (applied when text or identifier changes). The existing method signatures are preserved and delegate to the new overloads, keeping the change backwards compatible. The existing sourcefiles UpdateOption enum is reused, consistent with UploadStringsRequest. Closes #371 --- .../sourcestrings/SourceStringsApi.java | 40 ++++++++++++- .../sourcestrings/SourceStringsApiTest.java | 56 ++++++++++++++++++- 2 files changed, 93 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/crowdin/client/sourcestrings/SourceStringsApi.java b/src/main/java/com/crowdin/client/sourcestrings/SourceStringsApi.java index e21857566..2e6cad607 100644 --- a/src/main/java/com/crowdin/client/sourcestrings/SourceStringsApi.java +++ b/src/main/java/com/crowdin/client/sourcestrings/SourceStringsApi.java @@ -5,6 +5,7 @@ import com.crowdin.client.core.http.exceptions.HttpBadRequestException; import com.crowdin.client.core.http.exceptions.HttpException; import com.crowdin.client.core.model.*; +import com.crowdin.client.sourcefiles.model.UpdateOption; import com.crowdin.client.sourcestrings.model.*; import java.util.List; @@ -165,7 +166,25 @@ public void deleteSourceString(Long projectId, Long stringId) throws HttpExcepti * */ public ResponseObject editSourceString(Long projectId, Long stringId, List request) throws HttpException, HttpBadRequestException { - SourceStringResponseObject sourceStringResponseObject = this.httpClient.patch(this.url + "/projects/" + projectId + "/strings/" + stringId, request, new HttpRequestConfig(), SourceStringResponseObject.class); + return editSourceString(projectId, stringId, request, null); + } + + /** + * @param projectId project identifier + * @param stringId string identifier + * @param request request object + * @param updateOption defines whether existing translations and approvals are kept when the string is updated (applied only when {@code text} or {@code identifier} is changed) + * @return updated source string + * @see + */ + public ResponseObject editSourceString(Long projectId, Long stringId, List request, UpdateOption updateOption) throws HttpException, HttpBadRequestException { + HttpRequestConfig config = new HttpRequestConfig(HttpRequestConfig.buildUrlParams( + "updateOption", Optional.ofNullable(updateOption) + )); + SourceStringResponseObject sourceStringResponseObject = this.httpClient.patch(this.url + "/projects/" + projectId + "/strings/" + stringId, request, config, SourceStringResponseObject.class); return ResponseObject.of(sourceStringResponseObject.getData()); } @@ -179,8 +198,25 @@ public ResponseObject editSourceString(Long projectId, Long string * */ public ResponseList stringBatchOperations(Long projectId, List request) throws HttpException, HttpBadRequestException { + return stringBatchOperations(projectId, request, null); + } + + /** + * @param projectId project identifier + * @param request request object + * @param updateOption defines whether existing translations and approvals are kept when a string is updated (applied only when {@code text} or {@code identifier} is changed) + * @return updated source string + * @see + */ + public ResponseList stringBatchOperations(Long projectId, List request, UpdateOption updateOption) throws HttpException, HttpBadRequestException { + HttpRequestConfig config = new HttpRequestConfig(HttpRequestConfig.buildUrlParams( + "updateOption", Optional.ofNullable(updateOption) + )); String url = this.url + "/projects/" + projectId + "/strings"; - SourceStringResponseList sourceStringResponseList = this.httpClient.patch(url, request, new HttpRequestConfig(), SourceStringResponseList.class); + SourceStringResponseList sourceStringResponseList = this.httpClient.patch(url, request, config, SourceStringResponseList.class); return SourceStringResponseList.to(sourceStringResponseList); } } diff --git a/src/test/java/com/crowdin/client/sourcestrings/SourceStringsApiTest.java b/src/test/java/com/crowdin/client/sourcestrings/SourceStringsApiTest.java index f134cb8bb..ff6ae8365 100644 --- a/src/test/java/com/crowdin/client/sourcestrings/SourceStringsApiTest.java +++ b/src/test/java/com/crowdin/client/sourcestrings/SourceStringsApiTest.java @@ -14,7 +14,9 @@ import java.util.*; +import static java.util.Collections.emptyMap; import static java.util.Collections.singletonList; +import static java.util.Collections.singletonMap; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -53,7 +55,9 @@ public List getMocks() { RequestMock.build(this.url + "/projects/" + projectId + "/strings/" + id, HttpGet.METHOD_NAME, "api/strings/string.json"), RequestMock.build(this.url + "/projects/" + projectId + "/strings/" + id, HttpDelete.METHOD_NAME), RequestMock.build(this.url + "/projects/" + projectId + "/strings/" + id, HttpPatch.METHOD_NAME, "api/strings/editString.json", "api/strings/string.json"), - RequestMock.build(this.url + "/projects/" + projectId + "/strings", HttpPatch.METHOD_NAME, "api/strings/stringBatchOperationsRequest.json", "api/strings/listStrings.json") + RequestMock.build(this.url + "/projects/" + projectId + "/strings", HttpPatch.METHOD_NAME, "api/strings/stringBatchOperationsRequest.json", "api/strings/listStrings.json"), + new RequestMock(this.url + "/projects/" + projectId + "/strings/" + id2, "api/strings/editString.json", "api/strings/string.json", HttpPatch.METHOD_NAME, singletonMap("updateOption", UpdateOption.KEEP_TRANSLATIONS_AND_APPROVALS), emptyMap()), + new RequestMock(this.url + "/projects/" + project2Id + "/strings", "api/strings/stringBatchOperationsRequest.json", "api/strings/listStrings.json", HttpPatch.METHOD_NAME, singletonMap("updateOption", UpdateOption.CLEAR_TRANSLATIONS_AND_APPROVALS), emptyMap()) ); } @@ -325,4 +329,54 @@ public void stringBatchOperationsTest() { assertEquals(48, item.getFileId()); assertEquals(667, item.getBranchId()); } + + @Test + public void editStringWithUpdateOptionTest() { + PatchRequest request = new PatchRequest(); + request.setOp(PatchOperation.REPLACE); + request.setValue(text); + request.setPath("/text"); + ResponseObject sourceStringResponseObject = this.getSourceStringsApi() + .editSourceString(projectId, id2, singletonList(request), UpdateOption.KEEP_TRANSLATIONS_AND_APPROVALS); + assertEquals(sourceStringResponseObject.getData().getId(), id); + assertEquals(sourceStringResponseObject.getData().getText(), text); + } + + @Test + public void stringBatchOperationsWithUpdateOptionTest() { + List request = new ArrayList() {{ + add(new PatchRequest() {{ + setOp(PatchOperation.REPLACE); + setPath("/2814/isHidden"); + setValue(true); + }}); + add(new PatchRequest() {{ + setOp(PatchOperation.REPLACE); + setPath("/2814/context"); + setValue("some context"); + }}); + add(new PatchRequest() {{ + setOp(PatchOperation.ADD); + setPath("/-"); + setValue(new SourceStringForm() {{ + setText("new added string"); + setIdentifier("a.b.c"); + setContext("context for new string"); + setFileId(5L); + setIsHidden(false); + }}); + }}); + add(new PatchRequest() {{ + setOp(PatchOperation.REMOVE); + setPath("/2815"); + }}); + }}; + + ResponseList response = this.getSourceStringsApi() + .stringBatchOperations(project2Id, request, UpdateOption.CLEAR_TRANSLATIONS_AND_APPROVALS); + + SourceString item = response.getData().get(0).getData(); + assertNotNull(item); + assertEquals(2814, item.getId()); + } } From 8a02bd5c0fb524401ed20841e6d10e87ffe94adb Mon Sep 17 00:00:00 2001 From: GabrielBBaldez <130607246+GabrielBBaldez@users.noreply.github.com> Date: Mon, 8 Jun 2026 15:50:27 -0300 Subject: [PATCH 2/2] Address review: fix stringBatchOperations @return and align updateOption test fixtures - stringBatchOperations Javadoc now says it returns a list of source strings. - The editSourceString updateOption test now uses a separate project id with the existing string id, so the response fixture matches the requested resource while keeping the mock URL unique. --- .../com/crowdin/client/sourcestrings/SourceStringsApi.java | 4 ++-- .../crowdin/client/sourcestrings/SourceStringsApiTest.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/crowdin/client/sourcestrings/SourceStringsApi.java b/src/main/java/com/crowdin/client/sourcestrings/SourceStringsApi.java index 2e6cad607..21d850931 100644 --- a/src/main/java/com/crowdin/client/sourcestrings/SourceStringsApi.java +++ b/src/main/java/com/crowdin/client/sourcestrings/SourceStringsApi.java @@ -191,7 +191,7 @@ public ResponseObject editSourceString(Long projectId, Long string /** * @param projectId project identifier * @param request request object - * @return updated source string + * @return list of updated source strings * @see
    *
  • API Documentation
  • *
  • Enterprise API Documentation
  • @@ -205,7 +205,7 @@ public ResponseList stringBatchOperations(Long projectId, List *
  • API Documentation
  • *
  • Enterprise API Documentation
  • diff --git a/src/test/java/com/crowdin/client/sourcestrings/SourceStringsApiTest.java b/src/test/java/com/crowdin/client/sourcestrings/SourceStringsApiTest.java index ff6ae8365..7677b12dc 100644 --- a/src/test/java/com/crowdin/client/sourcestrings/SourceStringsApiTest.java +++ b/src/test/java/com/crowdin/client/sourcestrings/SourceStringsApiTest.java @@ -56,7 +56,7 @@ public List getMocks() { RequestMock.build(this.url + "/projects/" + projectId + "/strings/" + id, HttpDelete.METHOD_NAME), RequestMock.build(this.url + "/projects/" + projectId + "/strings/" + id, HttpPatch.METHOD_NAME, "api/strings/editString.json", "api/strings/string.json"), RequestMock.build(this.url + "/projects/" + projectId + "/strings", HttpPatch.METHOD_NAME, "api/strings/stringBatchOperationsRequest.json", "api/strings/listStrings.json"), - new RequestMock(this.url + "/projects/" + projectId + "/strings/" + id2, "api/strings/editString.json", "api/strings/string.json", HttpPatch.METHOD_NAME, singletonMap("updateOption", UpdateOption.KEEP_TRANSLATIONS_AND_APPROVALS), emptyMap()), + new RequestMock(this.url + "/projects/" + project2Id + "/strings/" + id, "api/strings/editString.json", "api/strings/string.json", HttpPatch.METHOD_NAME, singletonMap("updateOption", UpdateOption.KEEP_TRANSLATIONS_AND_APPROVALS), emptyMap()), new RequestMock(this.url + "/projects/" + project2Id + "/strings", "api/strings/stringBatchOperationsRequest.json", "api/strings/listStrings.json", HttpPatch.METHOD_NAME, singletonMap("updateOption", UpdateOption.CLEAR_TRANSLATIONS_AND_APPROVALS), emptyMap()) ); } @@ -337,7 +337,7 @@ public void editStringWithUpdateOptionTest() { request.setValue(text); request.setPath("/text"); ResponseObject sourceStringResponseObject = this.getSourceStringsApi() - .editSourceString(projectId, id2, singletonList(request), UpdateOption.KEEP_TRANSLATIONS_AND_APPROVALS); + .editSourceString(project2Id, id, singletonList(request), UpdateOption.KEEP_TRANSLATIONS_AND_APPROVALS); assertEquals(sourceStringResponseObject.getData().getId(), id); assertEquals(sourceStringResponseObject.getData().getText(), text); }