Skip to content

Commit aec4387

Browse files
authored
feat: add file translations APIs (#363)
1 parent 4805484 commit aec4387

9 files changed

Lines changed: 260 additions & 1 deletion

src/main/java/com/crowdin/client/ai/AIApi.java

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,79 @@ public ResponseObject<AiTranslate> aiTranslateStrings(Long userId, AiTranslateRe
603603
return ResponseObject.of(response.getData());
604604
}
605605

606+
/**
607+
* @param userId user identifier
608+
* @param request request body
609+
* @return file translation job
610+
* @see <ul>
611+
* <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>
612+
* <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>
613+
* </ul>
614+
*/
615+
public ResponseObject<AiFileTranslation> addAiFileTranslation(Long userId, AiFileTranslationAddRequest request) {
616+
String url = getAIPath(userId, "ai/file-translations");
617+
AiFileTranslationResponseObject response = this.httpClient.post(url, request, new HttpRequestConfig(), AiFileTranslationResponseObject.class);
618+
return ResponseObject.of(response.getData());
619+
}
620+
621+
/**
622+
* @param userId user identifier
623+
* @param jobIdentifier AI file translation job identifier
624+
* @return file translation job status
625+
* @see <ul>
626+
* <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>
627+
* <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>
628+
* </ul>
629+
*/
630+
public ResponseObject<AiFileTranslation> getAiFileTranslationStatus(Long userId, String jobIdentifier) {
631+
String url = getAIPath(userId, "ai/file-translations/" + jobIdentifier);
632+
AiFileTranslationResponseObject response = this.httpClient.get(url, new HttpRequestConfig(), AiFileTranslationResponseObject.class);
633+
return ResponseObject.of(response.getData());
634+
}
635+
636+
/**
637+
* @param userId user identifier
638+
* @param jobIdentifier AI file translation job identifier
639+
* @see <ul>
640+
* <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>
641+
* <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>
642+
* </ul>
643+
*/
644+
public void cancelAiFileTranslation(Long userId, String jobIdentifier) {
645+
String url = getAIPath(userId, "ai/file-translations/" + jobIdentifier);
646+
this.httpClient.delete(url, new HttpRequestConfig(), Void.class);
647+
}
648+
649+
/**
650+
* @param userId user identifier
651+
* @param jobIdentifier AI file translation job identifier
652+
* @return translated file download link
653+
* @see <ul>
654+
* <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>
655+
* <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>
656+
* </ul>
657+
*/
658+
public ResponseObject<DownloadLink> downloadAiFileTranslation(Long userId, String jobIdentifier) {
659+
String url = getAIPath(userId, "ai/file-translations/" + jobIdentifier + "/download");
660+
DownloadLinkResponseObject response = this.httpClient.get(url, new HttpRequestConfig(), DownloadLinkResponseObject.class);
661+
return ResponseObject.of(response.getData());
662+
}
663+
664+
/**
665+
* @param userId user identifier
666+
* @param jobIdentifier AI file translation job identifier
667+
* @return translated strings download link
668+
* @see <ul>
669+
* <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>
670+
* <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>
671+
* </ul>
672+
*/
673+
public ResponseObject<DownloadLink> downloadAiFileTranslationStrings(Long userId, String jobIdentifier) {
674+
String url = getAIPath(userId, "ai/file-translations/" + jobIdentifier + "/translations");
675+
DownloadLinkResponseObject response = this.httpClient.get(url, new HttpRequestConfig(), DownloadLinkResponseObject.class);
676+
return ResponseObject.of(response.getData());
677+
}
678+
606679
private String getAIPath(Long userId, String path) {
607680
return userId != null ? String.format("%s/users/%d/%s", this.url, userId, path) : String.format("%s/%s", this.url, path);
608681
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.crowdin.client.ai.model;
2+
3+
import lombok.Data;
4+
5+
import java.util.Date;
6+
7+
@Data
8+
public class AiFileTranslation {
9+
private String identifier;
10+
private String status;
11+
private Long progress;
12+
private Attributes attributes;
13+
private Date createdAt;
14+
private Date updatedAt;
15+
private Date startedAt;
16+
private Date finishedAt;
17+
18+
@Data
19+
public static class Attributes {
20+
private String stage;
21+
private FileTranslationError error;
22+
private String downloadName;
23+
private String sourceLanguageId;
24+
private String targetLanguageId;
25+
private String originalFileName;
26+
private String detectedType;
27+
private Integer parserVersion;
28+
}
29+
30+
@Data
31+
private static class FileTranslationError {
32+
private String stage;
33+
private String message;
34+
}
35+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.crowdin.client.ai.model;
2+
3+
import lombok.Data;
4+
5+
import java.util.List;
6+
7+
@Data
8+
public class AiFileTranslationAddRequest {
9+
private Long storageId;
10+
private String sourceLanguageId;
11+
private String targetLanguageId;
12+
private String type;
13+
private Integer parserVersion;
14+
private List<Long> tmIds;
15+
private List<Long> glossaryIds;
16+
private Long aiPromptId;
17+
private Long aiProviderId;
18+
private String aiModelId;
19+
private List<String> instructions;
20+
private List<Long> attachmentIds;
21+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.crowdin.client.ai.model;
2+
3+
import lombok.Data;
4+
5+
@Data
6+
public class AiFileTranslationResponseObject {
7+
private AiFileTranslation data;
8+
}

src/test/java/com/crowdin/client/ai/AIApiTest.java

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ public class AIApiTest extends TestClient {
6767
private static final String PROXY_CHAT = "%s/users/%d/ai/providers/%d/chat/completions";
6868
private static final String LIST_SUPPORTED_AI_PROVIDER_MODELS = "%s/users/%d/ai/providers/supported-models";
6969
private static final String AI_TRANSLATE_STRING = "%s/users/%d/ai/translate";
70+
private static final String AI_FILE_TRANSLATIONS = "%s/users/%d/ai/file-translations";
71+
private static final String AI_FILE_TRANSLATION = "%s/users/%d/ai/file-translations/%s";
72+
private static final String AI_FILE_TRANSLATION_DOWNLOAD = "%s/users/%d/ai/file-translations/%s/download";
73+
private static final String AI_FILE_TRANSLATION_STRINGS = "%s/users/%d/ai/file-translations/%s/translations";
7074

7175
@Override
7276
public List<RequestMock> getMocks() {
@@ -108,7 +112,12 @@ public List<RequestMock> getMocks() {
108112
RequestMock.build(String.format(AI_PROMPT, this.url, userId, aiPromptId), HttpPatch.METHOD_NAME, "api/ai/editPromptRequest.json", "api/ai/promptResponse.json"),
109113
RequestMock.build(String.format(PROXY_CHAT, this.url, userId, aiPromptId), HttpPost.METHOD_NAME, "api/ai/proxyChatCompletionRequest.json", "api/ai/proxyChatCompletionResponse.json"),
110114
RequestMock.build(String.format(LIST_SUPPORTED_AI_PROVIDER_MODELS, this.url, userId), HttpGet.METHOD_NAME, "api/ai/listSupportedAiProviderModels.json"),
111-
RequestMock.build(String.format(AI_TRANSLATE_STRING, this.url, userId), HttpPost.METHOD_NAME, "api/ai/aiTranslateRequest.json", "api/ai/aiTranslateResponse.json")
115+
RequestMock.build(String.format(AI_TRANSLATE_STRING, this.url, userId), HttpPost.METHOD_NAME, "api/ai/aiTranslateRequest.json", "api/ai/aiTranslateResponse.json"),
116+
RequestMock.build(String.format(AI_FILE_TRANSLATIONS, this.url, userId), HttpPost.METHOD_NAME, "api/ai/addAiFileTranslationRequest.json", "api/ai/aiFileTranslationResponse.json"),
117+
RequestMock.build(String.format(AI_FILE_TRANSLATION, this.url, userId, jobIdentifier), HttpGet.METHOD_NAME, "api/ai/aiFileTranslationResponse.json"),
118+
RequestMock.build(String.format(AI_FILE_TRANSLATION, this.url, userId, jobIdentifier), HttpDelete.METHOD_NAME),
119+
RequestMock.build(String.format(AI_FILE_TRANSLATION_DOWNLOAD, this.url, userId, jobIdentifier), HttpGet.METHOD_NAME, "api/ai/downloadAiFileTranslationResponse.json"),
120+
RequestMock.build(String.format(AI_FILE_TRANSLATION_STRINGS, this.url, userId, jobIdentifier), HttpGet.METHOD_NAME, "api/ai/downloadAiFileTranslationStringsResponse.json")
112121
);
113122
}
114123

@@ -554,4 +563,59 @@ public void aiTranslateStringsTest() {
554563
assertEquals(2, response.getData().getTranslations().size());
555564
assertEquals("Перекладений текст 1", response.getData().getTranslations().get(0));
556565
}
566+
567+
@Test
568+
public void addAiFileTranslationTest() {
569+
AiFileTranslationAddRequest request = new AiFileTranslationAddRequest();
570+
request.setStorageId(123L);
571+
request.setSourceLanguageId("en");
572+
request.setTargetLanguageId("uk");
573+
request.setType("xliff");
574+
request.setParserVersion(1);
575+
request.setTmIds(Collections.singletonList(123L));
576+
request.setGlossaryIds(Collections.singletonList(456L));
577+
request.setAiPromptId(789L);
578+
request.setAiProviderId(12L);
579+
request.setAiModelId("gpt-4.1");
580+
request.setInstructions(Collections.singletonList("Keep a formal tone"));
581+
request.setAttachmentIds(Collections.singletonList(123L));
582+
583+
ResponseObject<AiFileTranslation> response = this.getAiApi().addAiFileTranslation(userId, request);
584+
assertEquals(jobIdentifier, response.getData().getIdentifier());
585+
assertEquals(status, response.getData().getStatus());
586+
assertEquals(progress, response.getData().getProgress());
587+
assertEquals("translate", response.getData().getAttributes().getStage());
588+
assertEquals("en", response.getData().getAttributes().getSourceLanguageId());
589+
assertEquals("uk", response.getData().getAttributes().getTargetLanguageId());
590+
}
591+
592+
@Test
593+
public void getAiFileTranslationStatusTest() {
594+
ResponseObject<AiFileTranslation> response = this.getAiApi().getAiFileTranslationStatus(userId, jobIdentifier);
595+
assertEquals(jobIdentifier, response.getData().getIdentifier());
596+
assertEquals(status, response.getData().getStatus());
597+
assertEquals("file.pdf", response.getData().getAttributes().getDownloadName());
598+
assertEquals("chrome", response.getData().getAttributes().getDetectedType());
599+
}
600+
601+
@Test
602+
public void cancelAiFileTranslationTest() {
603+
this.getAiApi().cancelAiFileTranslation(userId, jobIdentifier);
604+
}
605+
606+
@Test
607+
public void downloadAiFileTranslationTest() {
608+
ResponseObject<DownloadLink> response = this.getAiApi().downloadAiFileTranslation(userId, jobIdentifier);
609+
assertNotNull(response.getData());
610+
assertNotNull(response.getData().getUrl());
611+
assertFalse(response.getData().getUrl().isEmpty());
612+
}
613+
614+
@Test
615+
public void downloadAiFileTranslationStringsTest() {
616+
ResponseObject<DownloadLink> response = this.getAiApi().downloadAiFileTranslationStrings(userId, jobIdentifier);
617+
assertNotNull(response.getData());
618+
assertNotNull(response.getData().getUrl());
619+
assertFalse(response.getData().getUrl().isEmpty());
620+
}
557621
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"storageId": 123,
3+
"sourceLanguageId": "en",
4+
"targetLanguageId": "uk",
5+
"type": "xliff",
6+
"parserVersion": 1,
7+
"tmIds": [
8+
123
9+
],
10+
"glossaryIds": [
11+
456
12+
],
13+
"aiPromptId": 789,
14+
"aiProviderId": 12,
15+
"aiModelId": "gpt-4.1",
16+
"instructions": [
17+
"Keep a formal tone"
18+
],
19+
"attachmentIds": [
20+
123
21+
]
22+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"data": {
3+
"identifier": "50fb3506-4127-4ba8-8296-f97dc7e3e0c3",
4+
"status": "finished",
5+
"progress": 100,
6+
"attributes": {
7+
"stage": "translate",
8+
"error": {
9+
"stage": "import",
10+
"message": "Failed to parse file"
11+
},
12+
"downloadName": "file.pdf",
13+
"sourceLanguageId": "en",
14+
"targetLanguageId": "uk",
15+
"originalFileName": "Sample_Chrome.json",
16+
"detectedType": "chrome",
17+
"parserVersion": 2
18+
},
19+
"createdAt": "2026-01-23T11:26:54+00:00",
20+
"updatedAt": "2026-01-23T11:26:54+00:00",
21+
"startedAt": "2026-01-23T11:26:54+00:00",
22+
"finishedAt": "2026-01-23T11:26:54+00:00"
23+
}
24+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"data": {
3+
"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}",
4+
"expireIn": "2019-09-20T10:31:21+00:00"
5+
}
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"data": {
3+
"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}",
4+
"expireIn": "2019-09-20T10:31:21+00:00"
5+
}
6+
}

0 commit comments

Comments
 (0)