feat(ai): add file translation methods support#367
Conversation
There was a problem hiding this comment.
Pull request overview
Adds Crowdin AI File Translation endpoint support to the AI executor layer, along with unit tests and test resources, enabling SDK consumers to start/cancel file translation jobs and download results.
Changes:
- Introduces request/response models for AI file translation jobs (
AiFileTranslationsRequest,AiFileTranslationsStatus,AiFileTranslationsStage). - Extends
IAiApiExecutor/AiApiExecutorwith methods to start a job, fetch status, cancel, and download translated artifacts. - Adds unit tests plus new
.resxfixtures and a shared download-link assertion helper.
Reviewed changes
Copilot reviewed 10 out of 12 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/Crowdin.Api.UnitTesting/Tests/AI/AiFileTranslationsTests.cs | Adds unit tests covering AI file translation start/status/cancel/download APIs. |
| tests/Crowdin.Api.UnitTesting/Resources/Common.resx | Adds shared download link JSON fixture. |
| tests/Crowdin.Api.UnitTesting/Resources/Common.Designer.cs | Strongly-typed accessor for Common.resx. |
| tests/Crowdin.Api.UnitTesting/Resources/AI_FileTranslations.resx | Adds request/response JSON fixtures for AI file translation endpoints. |
| tests/Crowdin.Api.UnitTesting/Resources/AI_FileTranslations.Designer.cs | Strongly-typed accessor for AI_FileTranslations.resx. |
| tests/Crowdin.Api.UnitTesting/Crowdin.Api.UnitTesting.csproj | Wires new .resx files and designers into the test project. |
| tests/Crowdin.Api.UnitTesting/CommonResponsesAssertUtils.cs | Adds shared assertions for DownloadLink responses. |
| src/Crowdin.Api/AI/IAiApiExecutor.cs | Adds new public executor methods for AI file translation workflows. |
| src/Crowdin.Api/AI/AiFileTranslationsStatus.cs | Adds response model for file translation job status. |
| src/Crowdin.Api/AI/AiFileTranslationsStage.cs | Adds enum for job stage values. |
| src/Crowdin.Api/AI/AiFileTranslationsRequest.cs | Adds request model for starting AI file translations. |
| src/Crowdin.Api/AI/AiApiExecutor.cs | Implements the new AI file translation endpoints. |
Files not reviewed (2)
- tests/Crowdin.Api.UnitTesting/Resources/AI_FileTranslations.Designer.cs: Language not supported
- tests/Crowdin.Api.UnitTesting/Resources/Common.Designer.cs: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| /// <a href="https://support.crowdin.com/developer/api/v2/#tag/AI/operation/api.users.ai.file-translations.download">Crowdin File Based API</a> | ||
| /// <a href="https://support.crowdin.com/developer/api/v2/string-based/#tag/AI/operation/api.users.ai.file-translations.download">Crowdin String Based API</a> | ||
| /// <a href="https://support.crowdin.com/developer/enterprise/api/v2/#tag/AI/operation/api.ai.file-translations.download">Crowdin Enterprise API</a> | ||
| /// </summary> |
There was a problem hiding this comment.
DownloadTranslatedFile is missing the [PublicAPI] attribute, while other public methods in this executor are consistently annotated. For consistency (and to match the other AI endpoints), add [PublicAPI] to this method as well.
| /// </summary> | |
| /// </summary> | |
| [PublicAPI] |
| /// <summary> | ||
| /// AI File Translations. Documentation: | ||
| /// <a href="https://support.crowdin.com/developer/api/v2/#tag/AI/operation/api.users.ai.file-translations.post">Crowdin File Based API</a> | ||
| /// <a href="https://support.crowdin.com/developer/api/v2/string-based/#tag/AI/operation/api.users.ai.file-translations.post">Crowdin String Based API</a> | ||
| /// <a href="https://support.crowdin.com/developer/enterprise/api/v2/#tag/AI/operation/api.ai.file-translations.post">Crowdin Enterprise API</a> | ||
| /// </summary> | ||
| Task<AiFileTranslationsStatus> AiFileTranslations(long? userId, AiFileTranslationsRequest request); | ||
|
|
There was a problem hiding this comment.
Method name AiFileTranslations(...) reads like a noun/resource name rather than an action, which is inconsistent with the rest of IAiApiExecutor (e.g., GenerateAiReport, TranslateStrings, DownloadAiReport). Consider renaming this to a verb-based name (e.g., StartFileTranslation / TranslateFile) so the public API is clearer and more consistent.
| public async Task<AiFileTranslationsStatus> AiFileTranslations(long? userId, AiFileTranslationsRequest request) | ||
| { | ||
| string url = AddUserIdIfAvailable(userId, "/ai/file-translations"); | ||
| CrowdinApiResult result = await _apiClient.SendPostRequest(url, request); | ||
| return _jsonParser.ParseResponseObject<AiFileTranslationsStatus>(result.JsonObject); | ||
| } | ||
|
|
There was a problem hiding this comment.
Implementation method name AiFileTranslations(...) is noun-like and inconsistent with the executor's other verb-based method names. If you rename the interface method for consistency/clarity, update this implementation accordingly to keep the public API self-explanatory.
| public async Task<AiFileTranslationsStatus> AiFileTranslations(long? userId, AiFileTranslationsRequest request) | |
| { | |
| string url = AddUserIdIfAvailable(userId, "/ai/file-translations"); | |
| CrowdinApiResult result = await _apiClient.SendPostRequest(url, request); | |
| return _jsonParser.ParseResponseObject<AiFileTranslationsStatus>(result.JsonObject); | |
| } | |
| public async Task<AiFileTranslationsStatus> StartFileTranslations(long? userId, AiFileTranslationsRequest request) | |
| { | |
| string url = AddUserIdIfAvailable(userId, "/ai/file-translations"); | |
| CrowdinApiResult result = await _apiClient.SendPostRequest(url, request); | |
| return _jsonParser.ParseResponseObject<AiFileTranslationsStatus>(result.JsonObject); | |
| } | |
| Task<AiFileTranslationsStatus> IAiApiExecutor.AiFileTranslations(long? userId, AiFileTranslationsRequest request) | |
| { | |
| return StartFileTranslations(userId, request); | |
| } |
| public static void Assert_DownloadLink(DownloadLink? expectedDownloadLink) | ||
| { | ||
| ArgumentNullException.ThrowIfNull(expectedDownloadLink); | ||
|
|
||
| Assert.Equal("https://test.com", expectedDownloadLink.Url); | ||
| Assert.Equal(DateTimeOffset.Parse("2019-09-20T10:31:21+00:00"), expectedDownloadLink.ExpireIn); |
There was a problem hiding this comment.
Assert_DownloadLink parameter name expectedDownloadLink is misleading because the method asserts against the actual response value passed in by callers. Rename it to something like downloadLink/actualDownloadLink to make intent clearer.
| public static void Assert_DownloadLink(DownloadLink? expectedDownloadLink) | |
| { | |
| ArgumentNullException.ThrowIfNull(expectedDownloadLink); | |
| Assert.Equal("https://test.com", expectedDownloadLink.Url); | |
| Assert.Equal(DateTimeOffset.Parse("2019-09-20T10:31:21+00:00"), expectedDownloadLink.ExpireIn); | |
| public static void Assert_DownloadLink(DownloadLink? downloadLink) | |
| { | |
| ArgumentNullException.ThrowIfNull(downloadLink); | |
| Assert.Equal("https://test.com", downloadLink.Url); | |
| Assert.Equal(DateTimeOffset.Parse("2019-09-20T10:31:21+00:00"), downloadLink.ExpireIn); |
Closes #360