Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions src/main/java/com/crowdin/client/ai/AIApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,79 @@ public ResponseObject<AiTranslate> aiTranslateStrings(Long userId, AiTranslateRe
return ResponseObject.of(response.getData());
}

/**
* @param userId user identifier
* @param request request body
* @return file translation job
* @see <ul>
* <li><a href="https://support.crowdin.com/developer/api/v2/#tag/AI/operation/api.users.ai.file-translations.post" target="_blank"><b>API Documentation</b></a></li>
* <li><a href="https://support.crowdin.com/developer/enterprise/api/v2/#tag/AI/operation/api.ai.file-translations.post" target="_blank"><b>Enterprise API Documentation</b></a></li>
* </ul>
*/
public ResponseObject<AiFileTranslation> addAiFileTranslation(Long userId, AiFileTranslationAddRequest request) {
String url = getAIPath(userId, "ai/file-translations");
AiFileTranslationResponseObject response = this.httpClient.post(url, request, new HttpRequestConfig(), AiFileTranslationResponseObject.class);
return ResponseObject.of(response.getData());
}

/**
* @param userId user identifier
* @param jobIdentifier AI file translation job identifier
* @return file translation job status
* @see <ul>
* <li><a href="https://support.crowdin.com/developer/api/v2/#tag/AI/operation/api.users.ai.file-translations.get" target="_blank"><b>API Documentation</b></a></li>
* <li><a href="https://support.crowdin.com/developer/enterprise/api/v2/#tag/AI/operation/api.ai.file-translations.get" target="_blank"><b>Enterprise API Documentation</b></a></li>
* </ul>
*/
public ResponseObject<AiFileTranslation> getAiFileTranslationStatus(Long userId, String jobIdentifier) {
String url = getAIPath(userId, "ai/file-translations/" + jobIdentifier);
AiFileTranslationResponseObject response = this.httpClient.get(url, new HttpRequestConfig(), AiFileTranslationResponseObject.class);
return ResponseObject.of(response.getData());
}

/**
* @param userId user identifier
* @param jobIdentifier AI file translation job identifier
* @see <ul>
* <li><a href="https://support.crowdin.com/developer/api/v2/#tag/AI/operation/api.users.ai.file-translations.delete" target="_blank"><b>API Documentation</b></a></li>
* <li><a href="https://support.crowdin.com/developer/enterprise/api/v2/#tag/AI/operation/api.ai.file-translations.delete" target="_blank"><b>Enterprise API Documentation</b></a></li>
* </ul>
*/
public void cancelAiFileTranslation(Long userId, String jobIdentifier) {
String url = getAIPath(userId, "ai/file-translations/" + jobIdentifier);
this.httpClient.delete(url, new HttpRequestConfig(), Void.class);
}

/**
* @param userId user identifier
* @param jobIdentifier AI file translation job identifier
* @return translated file download link
* @see <ul>
* <li><a href="https://support.crowdin.com/developer/api/v2/#tag/AI/operation/api.users.ai.file-translations.download" target="_blank"><b>API Documentation</b></a></li>
* <li><a href="https://support.crowdin.com/developer/enterprise/api/v2/#tag/AI/operation/api.ai.file-translations.download" target="_blank"><b>Enterprise API Documentation</b></a></li>
* </ul>
*/
public ResponseObject<DownloadLink> downloadAiFileTranslation(Long userId, String jobIdentifier) {
String url = getAIPath(userId, "ai/file-translations/" + jobIdentifier + "/download");
DownloadLinkResponseObject response = this.httpClient.get(url, new HttpRequestConfig(), DownloadLinkResponseObject.class);
return ResponseObject.of(response.getData());
}

/**
* @param userId user identifier
* @param jobIdentifier AI file translation job identifier
* @return translated strings download link
* @see <ul>
* <li><a href="https://support.crowdin.com/developer/api/v2/#tag/AI/operation/api.users.ai.file-translations.download-strings" target="_blank"><b>API Documentation</b></a></li>
* <li><a href="https://support.crowdin.com/developer/enterprise/api/v2/#tag/AI/operation/api.ai.file-translations.download-strings" target="_blank"><b>Enterprise API Documentation</b></a></li>
* </ul>
*/
public ResponseObject<DownloadLink> downloadAiFileTranslationStrings(Long userId, String jobIdentifier) {
String url = getAIPath(userId, "ai/file-translations/" + jobIdentifier + "/translations");
Comment thread
andrii-bodnar marked this conversation as resolved.
DownloadLinkResponseObject response = this.httpClient.get(url, new HttpRequestConfig(), DownloadLinkResponseObject.class);
return ResponseObject.of(response.getData());
}

private String getAIPath(Long userId, String path) {
return userId != null ? String.format("%s/users/%d/%s", this.url, userId, path) : String.format("%s/%s", this.url, path);
}
Expand Down
35 changes: 35 additions & 0 deletions src/main/java/com/crowdin/client/ai/model/AiFileTranslation.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.crowdin.client.ai.model;

import lombok.Data;

import java.util.Date;

@Data
public class AiFileTranslation {
private String identifier;
private String status;
private Long progress;
private Attributes attributes;
private Date createdAt;
private Date updatedAt;
private Date startedAt;
private Date finishedAt;

@Data
public static class Attributes {
private String stage;
private FileTranslationError error;
private String downloadName;
private String sourceLanguageId;
private String targetLanguageId;
private String originalFileName;
private String detectedType;
private Integer parserVersion;
}

@Data
private static class FileTranslationError {
private String stage;
private String message;
}
Comment thread
zshs000 marked this conversation as resolved.
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.crowdin.client.ai.model;

import lombok.Data;

import java.util.List;

@Data
public class AiFileTranslationAddRequest {
private Long storageId;
private String sourceLanguageId;
private String targetLanguageId;
private String type;
private Integer parserVersion;
private List<Long> tmIds;
private List<Long> glossaryIds;
private Long aiPromptId;
private Long aiProviderId;
private String aiModelId;
private List<String> instructions;
private List<Long> attachmentIds;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.crowdin.client.ai.model;

import lombok.Data;

@Data
public class AiFileTranslationResponseObject {
private AiFileTranslation data;
}
66 changes: 65 additions & 1 deletion src/test/java/com/crowdin/client/ai/AIApiTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ public class AIApiTest extends TestClient {
private static final String PROXY_CHAT = "%s/users/%d/ai/providers/%d/chat/completions";
private static final String LIST_SUPPORTED_AI_PROVIDER_MODELS = "%s/users/%d/ai/providers/supported-models";
private static final String AI_TRANSLATE_STRING = "%s/users/%d/ai/translate";
private static final String AI_FILE_TRANSLATIONS = "%s/users/%d/ai/file-translations";
private static final String AI_FILE_TRANSLATION = "%s/users/%d/ai/file-translations/%s";
private static final String AI_FILE_TRANSLATION_DOWNLOAD = "%s/users/%d/ai/file-translations/%s/download";
private static final String AI_FILE_TRANSLATION_STRINGS = "%s/users/%d/ai/file-translations/%s/translations";
Comment thread
andrii-bodnar marked this conversation as resolved.

@Override
public List<RequestMock> getMocks() {
Expand Down Expand Up @@ -108,7 +112,12 @@ public List<RequestMock> getMocks() {
RequestMock.build(String.format(AI_PROMPT, this.url, userId, aiPromptId), HttpPatch.METHOD_NAME, "api/ai/editPromptRequest.json", "api/ai/promptResponse.json"),
RequestMock.build(String.format(PROXY_CHAT, this.url, userId, aiPromptId), HttpPost.METHOD_NAME, "api/ai/proxyChatCompletionRequest.json", "api/ai/proxyChatCompletionResponse.json"),
RequestMock.build(String.format(LIST_SUPPORTED_AI_PROVIDER_MODELS, this.url, userId), HttpGet.METHOD_NAME, "api/ai/listSupportedAiProviderModels.json"),
RequestMock.build(String.format(AI_TRANSLATE_STRING, this.url, userId), HttpPost.METHOD_NAME, "api/ai/aiTranslateRequest.json", "api/ai/aiTranslateResponse.json")
RequestMock.build(String.format(AI_TRANSLATE_STRING, this.url, userId), HttpPost.METHOD_NAME, "api/ai/aiTranslateRequest.json", "api/ai/aiTranslateResponse.json"),
RequestMock.build(String.format(AI_FILE_TRANSLATIONS, this.url, userId), HttpPost.METHOD_NAME, "api/ai/addAiFileTranslationRequest.json", "api/ai/aiFileTranslationResponse.json"),
RequestMock.build(String.format(AI_FILE_TRANSLATION, this.url, userId, jobIdentifier), HttpGet.METHOD_NAME, "api/ai/aiFileTranslationResponse.json"),
RequestMock.build(String.format(AI_FILE_TRANSLATION, this.url, userId, jobIdentifier), HttpDelete.METHOD_NAME),
RequestMock.build(String.format(AI_FILE_TRANSLATION_DOWNLOAD, this.url, userId, jobIdentifier), HttpGet.METHOD_NAME, "api/ai/downloadAiFileTranslationResponse.json"),
RequestMock.build(String.format(AI_FILE_TRANSLATION_STRINGS, this.url, userId, jobIdentifier), HttpGet.METHOD_NAME, "api/ai/downloadAiFileTranslationStringsResponse.json")
Comment thread
andrii-bodnar marked this conversation as resolved.
);
}

Expand Down Expand Up @@ -554,4 +563,59 @@ public void aiTranslateStringsTest() {
assertEquals(2, response.getData().getTranslations().size());
assertEquals("Перекладений текст 1", response.getData().getTranslations().get(0));
}

@Test
public void addAiFileTranslationTest() {
AiFileTranslationAddRequest request = new AiFileTranslationAddRequest();
request.setStorageId(123L);
request.setSourceLanguageId("en");
request.setTargetLanguageId("uk");
request.setType("xliff");
request.setParserVersion(1);
request.setTmIds(Collections.singletonList(123L));
request.setGlossaryIds(Collections.singletonList(456L));
request.setAiPromptId(789L);
request.setAiProviderId(12L);
request.setAiModelId("gpt-4.1");
request.setInstructions(Collections.singletonList("Keep a formal tone"));
request.setAttachmentIds(Collections.singletonList(123L));

ResponseObject<AiFileTranslation> response = this.getAiApi().addAiFileTranslation(userId, request);
assertEquals(jobIdentifier, response.getData().getIdentifier());
assertEquals(status, response.getData().getStatus());
assertEquals(progress, response.getData().getProgress());
assertEquals("translate", response.getData().getAttributes().getStage());
assertEquals("en", response.getData().getAttributes().getSourceLanguageId());
assertEquals("uk", response.getData().getAttributes().getTargetLanguageId());
}

@Test
public void getAiFileTranslationStatusTest() {
ResponseObject<AiFileTranslation> response = this.getAiApi().getAiFileTranslationStatus(userId, jobIdentifier);
assertEquals(jobIdentifier, response.getData().getIdentifier());
assertEquals(status, response.getData().getStatus());
assertEquals("file.pdf", response.getData().getAttributes().getDownloadName());
assertEquals("chrome", response.getData().getAttributes().getDetectedType());
}

@Test
public void cancelAiFileTranslationTest() {
this.getAiApi().cancelAiFileTranslation(userId, jobIdentifier);
}

@Test
public void downloadAiFileTranslationTest() {
ResponseObject<DownloadLink> response = this.getAiApi().downloadAiFileTranslation(userId, jobIdentifier);
assertNotNull(response.getData());
assertNotNull(response.getData().getUrl());
assertFalse(response.getData().getUrl().isEmpty());
}

@Test
public void downloadAiFileTranslationStringsTest() {
ResponseObject<DownloadLink> response = this.getAiApi().downloadAiFileTranslationStrings(userId, jobIdentifier);
assertNotNull(response.getData());
assertNotNull(response.getData().getUrl());
assertFalse(response.getData().getUrl().isEmpty());
}
}
22 changes: 22 additions & 0 deletions src/test/resources/api/ai/addAiFileTranslationRequest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"storageId": 123,
"sourceLanguageId": "en",
"targetLanguageId": "uk",
"type": "xliff",
"parserVersion": 1,
"tmIds": [
123
],
"glossaryIds": [
456
],
"aiPromptId": 789,
"aiProviderId": 12,
"aiModelId": "gpt-4.1",
"instructions": [
"Keep a formal tone"
],
"attachmentIds": [
123
]
}
24 changes: 24 additions & 0 deletions src/test/resources/api/ai/aiFileTranslationResponse.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"data": {
"identifier": "50fb3506-4127-4ba8-8296-f97dc7e3e0c3",
"status": "finished",
"progress": 100,
"attributes": {
"stage": "translate",
"error": {
"stage": "import",
"message": "Failed to parse file"
},
"downloadName": "file.pdf",
"sourceLanguageId": "en",
"targetLanguageId": "uk",
"originalFileName": "Sample_Chrome.json",
"detectedType": "chrome",
"parserVersion": 2
},
"createdAt": "2026-01-23T11:26:54+00:00",
"updatedAt": "2026-01-23T11:26:54+00:00",
"startedAt": "2026-01-23T11:26:54+00:00",
"finishedAt": "2026-01-23T11:26:54+00:00"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"data": {
"url": "https://production-enterprise-importer.downloads.crowdin.com/992000002/2/14.xliff?response-content-disposition={disposition}&X-Amz-Content-Sha256={sha}&X-Amz-Algorithm={algorithm}&X-Amz-Credential={credentials}&X-Amz-Date={date}&X-Amz-SignedHeaders={headers}&X-Amz-Expires={expires}&X-Amz-Signature={signature}",
"expireIn": "2019-09-20T10:31:21+00:00"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"data": {
"url": "https://production-enterprise-importer.downloads.crowdin.com/992000002/2/14.xliff?response-content-disposition={disposition}&X-Amz-Content-Sha256={sha}&X-Amz-Algorithm={algorithm}&X-Amz-Credential={credentials}&X-Amz-Date={date}&X-Amz-SignedHeaders={headers}&X-Amz-Expires={expires}&X-Amz-Signature={signature}",
"expireIn": "2019-09-20T10:31:21+00:00"
}
}