diff --git a/backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj b/backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj index 1da4bfacc..2fff98d70 100644 --- a/backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj +++ b/backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj @@ -30,7 +30,7 @@ - + diff --git a/backend/src/Squidex.Infrastructure/Translations/DeepLTranslator.cs b/backend/src/Squidex.Infrastructure/Translations/DeepLTranslator.cs deleted file mode 100644 index 29023355c..000000000 --- a/backend/src/Squidex.Infrastructure/Translations/DeepLTranslator.cs +++ /dev/null @@ -1,101 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Net; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Options; -using Squidex.Infrastructure.Json; - -namespace Squidex.Infrastructure.Translations -{ - [ExcludeFromCodeCoverage] - public sealed class DeepLTranslator : ITranslator - { - private const string Url = "https://api.deepl.com/v2/translate"; - private readonly HttpClient httpClient = new HttpClient(); - private readonly DeepLTranslatorOptions deeplOptions; - private readonly IJsonSerializer jsonSerializer; - - private sealed class Response - { - public ResponseTranslation[] Translations { get; set; } - } - - private sealed class ResponseTranslation - { - public string Text { get; set; } - } - - public DeepLTranslator(IOptions deeplOptions, IJsonSerializer jsonSerializer) - { - Guard.NotNull(deeplOptions, nameof(deeplOptions)); - Guard.NotNull(jsonSerializer, nameof(jsonSerializer)); - - this.deeplOptions = deeplOptions.Value; - - this.jsonSerializer = jsonSerializer; - } - - public async Task Translate(string sourceText, Language targetLanguage, Language? sourceLanguage = null, CancellationToken ct = default) - { - if (string.IsNullOrWhiteSpace(sourceText) || targetLanguage == null) - { - return new Translation(TranslationResult.NotTranslated, sourceText); - } - - if (string.IsNullOrWhiteSpace(deeplOptions.AuthKey)) - { - return new Translation(TranslationResult.NotImplemented); - } - - var parameters = new Dictionary - { - ["auth_key"] = deeplOptions.AuthKey, - ["text"] = sourceText, - ["target_lang"] = GetLanguageCode(targetLanguage) - }; - - if (sourceLanguage != null) - { - parameters["source_lang"] = GetLanguageCode(sourceLanguage); - } - - var body = new FormUrlEncodedContent(parameters!); - - using (var response = await httpClient.PostAsync(Url, body, ct)) - { - var responseString = await response.Content.ReadAsStringAsync(ct); - - if (response.IsSuccessStatusCode) - { - var result = jsonSerializer.Deserialize(responseString); - - if (result?.Translations?.Length == 1) - { - return new Translation(TranslationResult.Translated, result.Translations[0].Text); - } - } - - if (response.StatusCode == HttpStatusCode.BadRequest) - { - return new Translation(TranslationResult.LanguageNotSupported, resultText: responseString); - } - - return new Translation(TranslationResult.Failed, resultText: responseString); - } - } - - private static string GetLanguageCode(Language language) - { - return language.Iso2Code.Substring(0, 2).ToUpperInvariant(); - } - } -} diff --git a/backend/src/Squidex.Infrastructure/Translations/DeepLTranslatorOptions.cs b/backend/src/Squidex.Infrastructure/Translations/DeepLTranslatorOptions.cs deleted file mode 100644 index 653e3062d..000000000 --- a/backend/src/Squidex.Infrastructure/Translations/DeepLTranslatorOptions.cs +++ /dev/null @@ -1,14 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -namespace Squidex.Infrastructure.Translations -{ - public sealed class DeepLTranslatorOptions - { - public string? AuthKey { get; set; } - } -} diff --git a/backend/src/Squidex.Infrastructure/Translations/ITranslator.cs b/backend/src/Squidex.Infrastructure/Translations/ITranslator.cs deleted file mode 100644 index 84f6df5df..000000000 --- a/backend/src/Squidex.Infrastructure/Translations/ITranslator.cs +++ /dev/null @@ -1,17 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Threading; -using System.Threading.Tasks; - -namespace Squidex.Infrastructure.Translations -{ - public interface ITranslator - { - Task Translate(string sourceText, Language targetLanguage, Language? sourceLanguage = null, CancellationToken ct = default); - } -} diff --git a/backend/src/Squidex.Infrastructure/Translations/NoopTranslator.cs b/backend/src/Squidex.Infrastructure/Translations/NoopTranslator.cs deleted file mode 100644 index 567d167fb..000000000 --- a/backend/src/Squidex.Infrastructure/Translations/NoopTranslator.cs +++ /dev/null @@ -1,22 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Threading; -using System.Threading.Tasks; - -namespace Squidex.Infrastructure.Translations -{ - public sealed class NoopTranslator : ITranslator - { - public Task Translate(string sourceText, Language targetLanguage, Language? sourceLanguage = null, CancellationToken ct = default) - { - var result = new Translation(TranslationResult.NotImplemented); - - return Task.FromResult(result); - } - } -} diff --git a/backend/src/Squidex.Infrastructure/Translations/Translation.cs b/backend/src/Squidex.Infrastructure/Translations/Translation.cs deleted file mode 100644 index 619259006..000000000 --- a/backend/src/Squidex.Infrastructure/Translations/Translation.cs +++ /dev/null @@ -1,25 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -namespace Squidex.Infrastructure.Translations -{ - public sealed class Translation - { - public TranslationResult Result { get; } - - public string? Text { get; } - - public string? ResultText { get; set; } - - public Translation(TranslationResult result, string? text = null, string? resultText = null) - { - Text = text; - Result = result; - ResultText = resultText; - } - } -} diff --git a/backend/src/Squidex.Infrastructure/Translations/TranslationResult.cs b/backend/src/Squidex.Infrastructure/Translations/TranslationResult.cs deleted file mode 100644 index f58f0ce45..000000000 --- a/backend/src/Squidex.Infrastructure/Translations/TranslationResult.cs +++ /dev/null @@ -1,18 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -namespace Squidex.Infrastructure.Translations -{ - public enum TranslationResult - { - Translated, - LanguageNotSupported, - NotTranslated, - NotImplemented, - Failed - } -} diff --git a/backend/src/Squidex/Areas/Api/Controllers/Translations/Models/TranslationDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Translations/Models/TranslationDto.cs index d2f528b90..8cf314fe0 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Translations/Models/TranslationDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Translations/Models/TranslationDto.cs @@ -7,6 +7,7 @@ using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Translations; +using Squidex.Text.Translations; namespace Squidex.Areas.Api.Controllers.Translations.Models { @@ -15,14 +16,14 @@ namespace Squidex.Areas.Api.Controllers.Translations.Models /// /// The result of the translation. /// - public TranslationResult Result { get; set; } + public TranslationResultCode Result { get; set; } /// /// The translated text. /// public string? Text { get; set; } - public static TranslationDto FromTranslation(Translation translation) + public static TranslationDto FromTranslation(TranslationResult translation) { return SimpleMapper.Map(translation, new TranslationDto()); } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Translations/TranslationsController.cs b/backend/src/Squidex/Areas/Api/Controllers/Translations/TranslationsController.cs index b4016457e..068f3e0ea 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Translations/TranslationsController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Translations/TranslationsController.cs @@ -11,6 +11,7 @@ using Squidex.Areas.Api.Controllers.Translations.Models; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Translations; using Squidex.Shared; +using Squidex.Text.Translations; using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Translations @@ -44,7 +45,7 @@ namespace Squidex.Areas.Api.Controllers.Translations [ApiCosts(0)] public async Task PostTranslation(string app, [FromBody] TranslateDto request) { - var result = await translator.Translate(request.Text, request.TargetLanguage, request.SourceLanguage, HttpContext.RequestAborted); + var result = await translator.TranslateAsync(request.Text, request.TargetLanguage, request.SourceLanguage, HttpContext.RequestAborted); var response = TranslationDto.FromTranslation(result); return Ok(response); diff --git a/backend/src/Squidex/Config/Domain/InfrastructureServices.cs b/backend/src/Squidex/Config/Domain/InfrastructureServices.cs index 948012389..efce25d96 100644 --- a/backend/src/Squidex/Config/Domain/InfrastructureServices.cs +++ b/backend/src/Squidex/Config/Domain/InfrastructureServices.cs @@ -9,6 +9,7 @@ using System; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; using NodaTime; using Orleans; using Squidex.Areas.Api.Controllers.Contents.Generator; @@ -31,6 +32,8 @@ using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.Translations; using Squidex.Infrastructure.UsageTracking; using Squidex.Pipeline.Robots; +using Squidex.Text.Translations; +using Squidex.Text.Translations.GoogleCloud; using Squidex.Web; using Squidex.Web.Pipeline; @@ -120,15 +123,23 @@ namespace Squidex.Config.Domain public static void AddSquidexTranslation(this IServiceCollection services, IConfiguration config) { - services.Configure( + services.Configure( config.GetSection("translations:deepL")); + services.Configure( + config.GetSection("translations:googleCloud")); services.Configure( config.GetSection("languages")); services.AddSingletonAs() .AsSelf(); - services.AddSingletonAs() + services.AddSingletonAs(c => new DeepLTranslationService(c.GetRequiredService>().Value)) + .As(); + + services.AddSingletonAs(c => new GoogleCloudTranslationService(c.GetRequiredService>().Value)) + .As(); + + services.AddSingletonAs() .As(); } diff --git a/backend/src/Squidex/appsettings.json b/backend/src/Squidex/appsettings.json index 1cbd48d94..df1358d62 100644 --- a/backend/src/Squidex/appsettings.json +++ b/backend/src/Squidex/appsettings.json @@ -672,11 +672,18 @@ }, "translations": { - /* - * The deepl api key if you want to support automated translations. - */ "deepl": { + /* + * The deepl api key if you want to support automated translations. + */ "authKey": "" + }, + + "googleCloud": { + /* + * The google cloud project id if you want to support automated translations. + */ + "projectId": "" } }, @@ -684,38 +691,38 @@ /* * Set to true to rebuild apps. */ - "apps": false, + "apps": false, - /* + /* * Set to true to rebuild assets. */ - "assets": false, + "assets": false, - /* + /* * Set to true to create dummy asset files if they do not exist. Useful when a backup fail. */ - "assetFiles": false, + "assetFiles": false, - /* + /* * Set to true to rebuild contents. */ - "contents": false, + "contents": false, - /* + /* * Set to true to rebuild rules. */ - "rules": false, + "rules": false, - /* + /* * Set to true to rebuild schemas. */ - "schemas": false, + "schemas": false, - /* + /* * Set to true to rebuild indexes. */ - "indexes": false - }, + "indexes": false + }, /*" * A list of configuration values that should be exposed from the info endpoint and in the UI.