diff --git a/src/Crowdin.Api/AI/AiApiExecutor.cs b/src/Crowdin.Api/AI/AiApiExecutor.cs index 304bd87d..01dac608 100644 --- a/src/Crowdin.Api/AI/AiApiExecutor.cs +++ b/src/Crowdin.Api/AI/AiApiExecutor.cs @@ -515,6 +515,22 @@ public async Task> ListSupportedAiProvide return _jsonParser.ParseResponseList(result.JsonObject); } + /// + /// AI Translate Strings. Documentation: + /// Crowdin File Based API + /// Crowdin String Based API + /// Crowdin Enterprise API + /// + [PublicAPI] + public async Task TranslateStrings( + long? userId, + AiTranslateStringsRequest request) + { + string url = AddUserIdIfAvailable(userId, "/ai/translate"); + CrowdinApiResult result = await _apiClient.SendPostRequest(url, request); + return _jsonParser.ParseResponseObject(result.JsonObject); + } + #region Helper methods private static string AddUserIdIfAvailable(long? userId, string baseUrl) diff --git a/src/Crowdin.Api/AI/AiTranslateStringsRequest.cs b/src/Crowdin.Api/AI/AiTranslateStringsRequest.cs new file mode 100644 index 00000000..58a7ff6f --- /dev/null +++ b/src/Crowdin.Api/AI/AiTranslateStringsRequest.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using JetBrains.Annotations; +using Newtonsoft.Json; + +namespace Crowdin.Api.AI +{ + [PublicAPI] + public class AiTranslateStringsRequest + { + [JsonProperty("strings")] + public ICollection Strings { get; set; } + + [JsonProperty("targetLanguageId")] + public string TargetLanguageId { get; set; } + + [JsonProperty("sourceLanguageId")] + public string? SourceLanguageId { get; set; } + + [JsonProperty("tmIds")] + public ICollection? TmIds { get; set; } + + [JsonProperty("glossaryIds")] + public ICollection? GlossaryIds { get; set; } + + [JsonProperty("aiPromptId")] + public long? AiPromptId { get; set; } + + [JsonProperty("aiProviderId")] + public long? AiProviderId { get; set; } + + [JsonProperty("aiModelId")] + public string? AiModelId { get; set; } + + [JsonProperty("instructions")] + public ICollection? Instructions { get; set; } + + [JsonProperty("attachmentIds")] + public ICollection? AttachmentIds { get; set; } + } +} \ No newline at end of file diff --git a/src/Crowdin.Api/AI/AiTranslateStringsResponse.cs b/src/Crowdin.Api/AI/AiTranslateStringsResponse.cs new file mode 100644 index 00000000..4c56b9d4 --- /dev/null +++ b/src/Crowdin.Api/AI/AiTranslateStringsResponse.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using JetBrains.Annotations; +using Newtonsoft.Json; + +namespace Crowdin.Api.AI +{ + [PublicAPI] + public class AiTranslateStringsResponse + { + [JsonProperty("sourceLanguageId")] + public string SourceLanguageId { get; set; } = null!; + + [JsonProperty("targetLanguageId")] + public string TargetLanguageId { get; set; } = null!; + + [JsonProperty("translations")] + public ICollection Translations { get; set; } = null!; + } +} \ No newline at end of file diff --git a/src/Crowdin.Api/AI/IAiApiExecutor.cs b/src/Crowdin.Api/AI/IAiApiExecutor.cs index 7e128656..054d88d5 100644 --- a/src/Crowdin.Api/AI/IAiApiExecutor.cs +++ b/src/Crowdin.Api/AI/IAiApiExecutor.cs @@ -120,5 +120,8 @@ Task> ListSupportedAiProviderModels( string? providerType = null, bool? enabled = null, string? orderBy = null); + Task TranslateStrings( + long? userId, + AiTranslateStringsRequest request); } } \ No newline at end of file diff --git a/tests/Crowdin.Api.UnitTesting/Crowdin.Api.UnitTesting.csproj b/tests/Crowdin.Api.UnitTesting/Crowdin.Api.UnitTesting.csproj index 986e4463..7008fe3d 100644 --- a/tests/Crowdin.Api.UnitTesting/Crowdin.Api.UnitTesting.csproj +++ b/tests/Crowdin.Api.UnitTesting/Crowdin.Api.UnitTesting.csproj @@ -265,6 +265,10 @@ ResXFileCodeGenerator Tasks_Comments.Designer.cs + + ResXFileCodeGenerator + AI_TranslateStrings.Designer.cs + @@ -563,6 +567,11 @@ True Tasks_Comments.resx + + True + True + AI_TranslateStrings.resx + diff --git a/tests/Crowdin.Api.UnitTesting/Resources/AI_TranslateStrings.Designer.cs b/tests/Crowdin.Api.UnitTesting/Resources/AI_TranslateStrings.Designer.cs new file mode 100644 index 00000000..6f452b93 --- /dev/null +++ b/tests/Crowdin.Api.UnitTesting/Resources/AI_TranslateStrings.Designer.cs @@ -0,0 +1,60 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Crowdin.Api.UnitTesting.Resources { + using System; + + + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class AI_TranslateStrings { + + private static System.Resources.ResourceManager resourceMan; + + private static System.Globalization.CultureInfo resourceCulture; + + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal AI_TranslateStrings() { + } + + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + internal static System.Resources.ResourceManager ResourceManager { + get { + if (object.Equals(null, resourceMan)) { + System.Resources.ResourceManager temp = new System.Resources.ResourceManager("Crowdin.Api.UnitTesting.Resources.AI_TranslateStrings", typeof(AI_TranslateStrings).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + internal static System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + internal static string TranslateStrings_Request { + get { + return ResourceManager.GetString("TranslateStrings_Request", resourceCulture); + } + } + + internal static string CommonResponses_TranslateStrings { + get { + return ResourceManager.GetString("CommonResponses_TranslateStrings", resourceCulture); + } + } + } +} diff --git a/tests/Crowdin.Api.UnitTesting/Resources/AI_TranslateStrings.resx b/tests/Crowdin.Api.UnitTesting/Resources/AI_TranslateStrings.resx new file mode 100644 index 00000000..58f94f47 --- /dev/null +++ b/tests/Crowdin.Api.UnitTesting/Resources/AI_TranslateStrings.resx @@ -0,0 +1,55 @@ + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + { + "strings": [ + "Some text to translate!" + ], + "targetLanguageId": "uk", + "sourceLanguageId": "en", + "tmIds": [ + 123 + ], + "glossaryIds": [ + 456 + ], + "aiPromptId": 789, + "instructions": [ + "Keep a formal tone" + ], + "attachmentIds": [ + 123 + ] +} + + + { + "data": { + "sourceLanguageId": "en", + "targetLanguageId": "uk", + "translations": [ + "Перекладений текст 1", + "Перекладений текст 2" + ] + } +} + + \ No newline at end of file diff --git a/tests/Crowdin.Api.UnitTesting/Tests/AI/AiTranslateStringsApiTests.cs b/tests/Crowdin.Api.UnitTesting/Tests/AI/AiTranslateStringsApiTests.cs new file mode 100644 index 00000000..fe85e779 --- /dev/null +++ b/tests/Crowdin.Api.UnitTesting/Tests/AI/AiTranslateStringsApiTests.cs @@ -0,0 +1,66 @@ +using System; +using System.Net; +using System.Threading.Tasks; +using Crowdin.Api.AI; +using Crowdin.Api.Core; +using Crowdin.Api.UnitTesting.Resources; +using Moq; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Xunit; + +namespace Crowdin.Api.UnitTesting.Tests.AI; + +public class AiTranslateStringsApiTests +{ + private static readonly JsonSerializerSettings JsonSettings = TestUtils.CreateJsonSerializerOptions(); + + [Fact] + public async Task TranslateStrings() + { + const int userId = 1; + + var request = new AiTranslateStringsRequest + { + Strings = new[] { "Some text to translate!" }, + TargetLanguageId = "uk", + SourceLanguageId = "en", + TmIds = new[] { 123L }, + GlossaryIds = new[] { 456L }, + AiPromptId = 789, + Instructions = new[] { "Keep a formal tone" }, + AttachmentIds = new[] { 123L } + }; + + string actualRequestJson = JsonConvert.SerializeObject(request, JsonSettings); + string expectedRequestJson = TestUtils.CompactJson(AI_TranslateStrings.TranslateStrings_Request); + Assert.Equal(expectedRequestJson, actualRequestJson); + + Mock mockClient = TestUtils.CreateMockClientWithDefaultParser(); + + var url = $"/users/{userId}/ai/translate"; + + mockClient + .Setup(client => client.SendPostRequest(url, request, null)) + .ReturnsAsync(new CrowdinApiResult + { + StatusCode = HttpStatusCode.OK, + JsonObject = JObject.Parse(AI_TranslateStrings.CommonResponses_TranslateStrings) + }); + + var executor = new AiApiExecutor(mockClient.Object); + AiTranslateStringsResponse response = await executor.TranslateStrings(userId, request); + + Assert_TranslateStrings(response); + } + + private static void Assert_TranslateStrings(AiTranslateStringsResponse? response) + { + ArgumentNullException.ThrowIfNull(response); + + Assert.Equal("en", response.SourceLanguageId); + Assert.Equal("uk", response.TargetLanguageId); + Assert.NotNull(response.Translations); + Assert.Equal(2, response.Translations.Count); + } +} \ No newline at end of file