From df115eed2ebbe4fc52f37bd155efb927f20a6d2e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Mar 2026 10:26:59 +0000 Subject: [PATCH 1/3] Initial plan From b36000bc9bd45216ec48d8445d34bd624d5b56d6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Mar 2026 10:33:01 +0000 Subject: [PATCH 2/3] feat: add support for AI File Translation methods Co-authored-by: andrii-bodnar <29282228+andrii-bodnar@users.noreply.github.com> --- src/ai/index.ts | 92 ++++++++++++++++++++++++++++++++++++++++++++ tests/ai/api.test.ts | 80 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 172 insertions(+) diff --git a/src/ai/index.ts b/src/ai/index.ts index 163ebdcaf..d9df4041e 100644 --- a/src/ai/index.ts +++ b/src/ai/index.ts @@ -997,6 +997,67 @@ export class Ai extends CrowdinApi { return this.post(url, request, this.defaultConfig()); } + + /** + * @param userId user identifier + * @param request request body + * @see https://support.crowdin.com/developer/api/v2/#tag/AI/operation/api.users.ai.file-translations.post + */ + startAiUserFileTranslation( + userId: number, + request: AiModel.AiFileTranslationRequest, + ): Promise>> { + const url = `${this.url}/users/${userId}/ai/file-translations`; + + return this.post(url, request, this.defaultConfig()); + } + + /** + * @param userId user identifier + * @param jobIdentifier job identifier + * @see https://support.crowdin.com/developer/api/v2/#tag/AI/operation/api.users.ai.file-translations.get + */ + getAiUserFileTranslationStatus( + userId: number, + jobIdentifier: string, + ): Promise>> { + const url = `${this.url}/users/${userId}/ai/file-translations/${jobIdentifier}`; + + return this.get(url, this.defaultConfig()); + } + + /** + * @param userId user identifier + * @param jobIdentifier job identifier + * @see https://support.crowdin.com/developer/api/v2/#tag/AI/operation/api.users.ai.file-translations.delete + */ + cancelAiUserFileTranslation(userId: number, jobIdentifier: string): Promise { + const url = `${this.url}/users/${userId}/ai/file-translations/${jobIdentifier}`; + + return this.delete(url, this.defaultConfig()); + } + + /** + * @param userId user identifier + * @param jobIdentifier job identifier + * @see https://support.crowdin.com/developer/api/v2/#tag/AI/operation/api.users.ai.file-translations.download + */ + downloadAiUserFileTranslation(userId: number, jobIdentifier: string): Promise> { + const url = `${this.url}/users/${userId}/ai/file-translations/${jobIdentifier}/download`; + + return this.get(url, this.defaultConfig()); + } + + /** + * @param userId user identifier + * @param jobIdentifier job identifier + * @see https://support.crowdin.com/developer/api/v2/#tag/AI/operation/api.users.ai.file-translations.download-strings + */ + downloadAiUserFileTranslationStrings(userId: number, jobIdentifier: string): Promise> { + const url = `${this.url}/users/${userId}/ai/file-translations/${jobIdentifier}/translations`; + + return this.get(url, this.defaultConfig()); + } } export namespace AiModel { @@ -1432,6 +1493,37 @@ export namespace AiModel { } /* ai Settings Section END*/ + /* ai File Translation Section START*/ + export interface AiFileTranslationRequest { + storageId: number; + targetLanguageId: string; + sourceLanguageId?: string; + type?: string; + parserVersion?: number; + tmIds?: number[]; + glossaryIds?: number[]; + aiPromptId?: number; + aiProviderId?: number; + aiModelId?: string; + instructions?: string[]; + attachmentIds?: number[]; + } + + export interface AiFileTranslationAttribute { + stage: string; + error: { + stage: string; + message: string; + } | null; + downloadName: string | null; + sourceLanguageId: string | null; + targetLanguageId: string; + originalFileName: string; + detectedType: string | null; + parserVersion: number | null; + } + /* ai File Translation Section END*/ + /* ai Translate Strings Section START*/ export interface AiTranslateStringsRequest { strings: string[]; diff --git a/tests/ai/api.test.ts b/tests/ai/api.test.ts index 9dec20b28..33b417ead 100644 --- a/tests/ai/api.test.ts +++ b/tests/ai/api.test.ts @@ -1079,6 +1079,59 @@ describe('AI API', () => { targetLanguageId: 'uk', translations: ['Перекладений текст'], }, + }) + .post( + `/users/${userId}/ai/file-translations`, + { + storageId: 1, + targetLanguageId: 'uk', + }, + { + reqheaders: { + Authorization: `Bearer ${api.token}`, + }, + }, + ) + .reply(200, { + data: { + identifier: jobId, + }, + }) + .get(`/users/${userId}/ai/file-translations/${jobId}`, undefined, { + reqheaders: { + Authorization: `Bearer ${api.token}`, + }, + }) + .reply(200, { + data: { + identifier: jobId, + }, + }) + .delete(`/users/${userId}/ai/file-translations/${jobId}`, undefined, { + reqheaders: { + Authorization: `Bearer ${api.token}`, + }, + }) + .reply(200) + .get(`/users/${userId}/ai/file-translations/${jobId}/download`, undefined, { + reqheaders: { + Authorization: `Bearer ${api.token}`, + }, + }) + .reply(200, { + data: { + url: link, + }, + }) + .get(`/users/${userId}/ai/file-translations/${jobId}/translations`, undefined, { + reqheaders: { + Authorization: `Bearer ${api.token}`, + }, + }) + .reply(200, { + data: { + url: link, + }, }); }); @@ -1569,4 +1622,31 @@ describe('AI API', () => { expect(res.data.targetLanguageId).toBe('uk'); expect(res.data.translations).toStrictEqual(['Перекладений текст']); }); + + it('Start AI User File Translation', async () => { + const res = await api.startAiUserFileTranslation(userId, { + storageId: 1, + targetLanguageId: 'uk', + }); + expect(res.data.identifier).toBe(jobId); + }); + + it('Get AI User File Translation Status', async () => { + const res = await api.getAiUserFileTranslationStatus(userId, jobId); + expect(res.data.identifier).toBe(jobId); + }); + + it('Cancel AI User File Translation', async () => { + await api.cancelAiUserFileTranslation(userId, jobId); + }); + + it('Download AI User File Translation', async () => { + const res = await api.downloadAiUserFileTranslation(userId, jobId); + expect(res.data.url).toBe(link); + }); + + it('Download AI User File Translation Strings', async () => { + const res = await api.downloadAiUserFileTranslationStrings(userId, jobId); + expect(res.data.url).toBe(link); + }); }); From 305102937fe4a0607bf599f625f5f21129adc8d0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Mar 2026 10:39:03 +0000 Subject: [PATCH 3/3] feat: add Enterprise (Organization) AI File Translation methods Co-authored-by: andrii-bodnar <29282228+andrii-bodnar@users.noreply.github.com> --- src/ai/index.ts | 54 ++++++++++++++++++++++++++++++ tests/ai/api.test.ts | 80 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 134 insertions(+) diff --git a/src/ai/index.ts b/src/ai/index.ts index d9df4041e..5cd566635 100644 --- a/src/ai/index.ts +++ b/src/ai/index.ts @@ -466,6 +466,60 @@ export class Ai extends CrowdinApi { return this.post(url, request, this.defaultConfig()); } + /** + * @param request request body + * @see https://support.crowdin.com/developer/enterprise/api/v2/#tag/AI/operation/api.ai.file-translations.post + */ + startAiOrganizationFileTranslation( + request: AiModel.AiFileTranslationRequest, + ): Promise>> { + const url = `${this.url}/ai/file-translations`; + + return this.post(url, request, this.defaultConfig()); + } + + /** + * @param jobIdentifier job identifier + * @see https://support.crowdin.com/developer/enterprise/api/v2/#tag/AI/operation/api.ai.file-translations.get + */ + getAiOrganizationFileTranslationStatus( + jobIdentifier: string, + ): Promise>> { + const url = `${this.url}/ai/file-translations/${jobIdentifier}`; + + return this.get(url, this.defaultConfig()); + } + + /** + * @param jobIdentifier job identifier + * @see https://support.crowdin.com/developer/enterprise/api/v2/#tag/AI/operation/api.ai.file-translations.delete + */ + cancelAiOrganizationFileTranslation(jobIdentifier: string): Promise { + const url = `${this.url}/ai/file-translations/${jobIdentifier}`; + + return this.delete(url, this.defaultConfig()); + } + + /** + * @param jobIdentifier job identifier + * @see https://support.crowdin.com/developer/enterprise/api/v2/#tag/AI/operation/api.ai.file-translations.download + */ + downloadAiOrganizationFileTranslation(jobIdentifier: string): Promise> { + const url = `${this.url}/ai/file-translations/${jobIdentifier}/download`; + + return this.get(url, this.defaultConfig()); + } + + /** + * @param jobIdentifier job identifier + * @see https://support.crowdin.com/developer/enterprise/api/v2/#tag/AI/operation/api.ai.file-translations.download-strings + */ + downloadAiOrganizationFileTranslationStrings(jobIdentifier: string): Promise> { + const url = `${this.url}/ai/file-translations/${jobIdentifier}/translations`; + + return this.get(url, this.defaultConfig()); + } + // Community /** diff --git a/tests/ai/api.test.ts b/tests/ai/api.test.ts index 33b417ead..b49fcb39f 100644 --- a/tests/ai/api.test.ts +++ b/tests/ai/api.test.ts @@ -573,6 +573,59 @@ describe('AI API', () => { translations: ['Перекладений текст'], }, }) + .post( + '/ai/file-translations', + { + storageId: 1, + targetLanguageId: 'uk', + }, + { + reqheaders: { + Authorization: `Bearer ${api.token}`, + }, + }, + ) + .reply(200, { + data: { + identifier: jobId, + }, + }) + .get(`/ai/file-translations/${jobId}`, undefined, { + reqheaders: { + Authorization: `Bearer ${api.token}`, + }, + }) + .reply(200, { + data: { + identifier: jobId, + }, + }) + .delete(`/ai/file-translations/${jobId}`, undefined, { + reqheaders: { + Authorization: `Bearer ${api.token}`, + }, + }) + .reply(200) + .get(`/ai/file-translations/${jobId}/download`, undefined, { + reqheaders: { + Authorization: `Bearer ${api.token}`, + }, + }) + .reply(200, { + data: { + url: link, + }, + }) + .get(`/ai/file-translations/${jobId}/translations`, undefined, { + reqheaders: { + Authorization: `Bearer ${api.token}`, + }, + }) + .reply(200, { + data: { + url: link, + }, + }) .get(`/users/${userId}/ai/settings/custom-placeholders`, undefined, { reqheaders: { Authorization: `Bearer ${api.token}`, @@ -1381,6 +1434,33 @@ describe('AI API', () => { expect(res.data.translations).toStrictEqual(['Перекладений текст']); }); + it('Start AI Organization File Translation', async () => { + const res = await api.startAiOrganizationFileTranslation({ + storageId: 1, + targetLanguageId: 'uk', + }); + expect(res.data.identifier).toBe(jobId); + }); + + it('Get AI Organization File Translation Status', async () => { + const res = await api.getAiOrganizationFileTranslationStatus(jobId); + expect(res.data.identifier).toBe(jobId); + }); + + it('Cancel AI Organization File Translation', async () => { + await api.cancelAiOrganizationFileTranslation(jobId); + }); + + it('Download AI Organization File Translation', async () => { + const res = await api.downloadAiOrganizationFileTranslation(jobId); + expect(res.data.url).toBe(link); + }); + + it('Download AI Organization File Translation Strings', async () => { + const res = await api.downloadAiOrganizationFileTranslationStrings(jobId); + expect(res.data.url).toBe(link); + }); + it('List AI User Custom Placeholders', async () => { const placeholders = await api.listAiUserCustomPlaceholders(userId); expect(placeholders.data.length).toBe(1);