From 43d84d341a88bc263813398f05c7d2481d01bb18 Mon Sep 17 00:00:00 2001 From: Stefan Sorgen Date: Tue, 3 Sep 2024 12:22:38 +0200 Subject: [PATCH 1/8] DeepL: added config and procedures for using a glossary by Id or Name --- text/Squidex.Text/Squidex.Text.csproj | 3 + .../DeepL/DeepLTranslationOptions.cs | 3 + .../DeepL/DeepLTranslationService.cs | 72 +++++++++++++++++++ 3 files changed, 78 insertions(+) diff --git a/text/Squidex.Text/Squidex.Text.csproj b/text/Squidex.Text/Squidex.Text.csproj index 553d3d6..0c9afa7 100644 --- a/text/Squidex.Text/Squidex.Text.csproj +++ b/text/Squidex.Text/Squidex.Text.csproj @@ -5,6 +5,9 @@ Latest enable enable + 5.18.0.1 + 5.18.0.1 + 5.18.0.1 diff --git a/text/Squidex.Text/Translations/DeepL/DeepLTranslationOptions.cs b/text/Squidex.Text/Translations/DeepL/DeepLTranslationOptions.cs index eb6f27c..2ea68bf 100644 --- a/text/Squidex.Text/Translations/DeepL/DeepLTranslationOptions.cs +++ b/text/Squidex.Text/Translations/DeepL/DeepLTranslationOptions.cs @@ -11,6 +11,9 @@ public sealed class DeepLTranslationOptions { public string AuthKey { get; set; } + public string GlossaryById { get; set; } // glossary_id + public string GlossaryByName { get; set; } // glossary_id + public decimal CostsPerCharacterInEUR { get; set; } = 20m / 1_000_000; public Dictionary Mapping { get; set; } diff --git a/text/Squidex.Text/Translations/DeepL/DeepLTranslationService.cs b/text/Squidex.Text/Translations/DeepL/DeepLTranslationService.cs index 7f0030f..895f3d8 100644 --- a/text/Squidex.Text/Translations/DeepL/DeepLTranslationService.cs +++ b/text/Squidex.Text/Translations/DeepL/DeepLTranslationService.cs @@ -36,6 +36,36 @@ private sealed class TranslationDto public string DetectedSourceLanguage { get; set; } } + private sealed class GlossariesDto + { + [JsonPropertyName("glossaries")] + public GlossaryDto[] Glossaries { get; set; } + } + + private sealed class GlossaryDto + { + [JsonPropertyName("glossary_id")] + public string GlossaryId { get; set; } // "def3a26b-3e84-45b3-84ae-0c0aaf3525f7" + + [JsonPropertyName("name")] + public string Name { get; set; } // "My Glossary" + + [JsonPropertyName("ready")] + public bool Ready { get; set; } // true + + [JsonPropertyName("source_lang")] + public string SourceLang { get; set; } // "EN" + + [JsonPropertyName("target_lang")] + public string TargetLang { get; set; } // "DE" + + [JsonPropertyName("creation_time")] + public DateTime CreationTime { get; set; } // "2021-08-03T14:16:18.329Z" + + [JsonPropertyName("entry_count")] + public int EntryCount { get; set; } // 1 + } + public DeepLTranslationService(IOptions options, IHttpClientFactory httpClientFactory) { this.options = options.Value; @@ -79,6 +109,48 @@ public async Task> TranslateAsync(IEnumerable("source_lang", GetLanguageCode(sourceLanguage))); } + if (!string.IsNullOrWhiteSpace(options.GlossaryByName) && string.IsNullOrWhiteSpace(options.GlossaryById)) + { + var gl_url = + (options.AuthKey.EndsWith(":fx", StringComparison.Ordinal) ? + UrlFree : + UrlPaid).Replace("/translate", "/glossaries"); + + var gl_requestMessage = new HttpRequestMessage(HttpMethod.Get, gl_url); + gl_requestMessage.Headers.Add("Authorization", $"DeepL-Auth-Key {options.AuthKey}"); + var gl_httpClient = httpClientFactory.CreateClient("DeepL"); + + using (var gl_response = await gl_httpClient.SendAsync(gl_requestMessage, ct)) + { + try + { + gl_response.EnsureSuccessStatusCode(); + + var jsonString = await gl_response.Content.ReadAsStringAsync(ct); + var jsonResponse = JsonSerializer.Deserialize(jsonString)!; + + var index = 0; + foreach (var glossary in jsonResponse.Glossaries) + { + if (options.GlossaryByName.Equals(glossary.Name, StringComparison.Ordinal)) + { + options.GlossaryById = glossary.GlossaryId; + break; + } + } + } + catch (Exception ex) + { + AddError(TranslationResult.Failed(ex)); + } + } + } + + if (!string.IsNullOrWhiteSpace(options.GlossaryById)) + { + parameters.Add(new KeyValuePair("glossary_id", options.GlossaryById)); + } + var url = options.AuthKey.EndsWith(":fx", StringComparison.Ordinal) ? UrlFree : From 8257e5f1b060dc23980555f1415467b9e725a12d Mon Sep 17 00:00:00 2001 From: Stefan Sorgen Date: Mon, 9 Sep 2024 11:03:21 +0200 Subject: [PATCH 2/8] DeepL: added tag_handling parameter for html-translation --- text/Squidex.Text/Squidex.Text.csproj | 8 ++++---- .../Translations/DeepL/DeepLTranslationOptions.cs | 3 ++- .../Translations/DeepL/DeepLTranslationService.cs | 5 +++++ 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/text/Squidex.Text/Squidex.Text.csproj b/text/Squidex.Text/Squidex.Text.csproj index 0c9afa7..25d812b 100644 --- a/text/Squidex.Text/Squidex.Text.csproj +++ b/text/Squidex.Text/Squidex.Text.csproj @@ -1,13 +1,13 @@ - + net7.0 Latest enable enable - 5.18.0.1 - 5.18.0.1 - 5.18.0.1 + 5.18.0.2 + 5.18.0.2 + 5.18.0.2 diff --git a/text/Squidex.Text/Translations/DeepL/DeepLTranslationOptions.cs b/text/Squidex.Text/Translations/DeepL/DeepLTranslationOptions.cs index 2ea68bf..9fce57d 100644 --- a/text/Squidex.Text/Translations/DeepL/DeepLTranslationOptions.cs +++ b/text/Squidex.Text/Translations/DeepL/DeepLTranslationOptions.cs @@ -12,7 +12,8 @@ public sealed class DeepLTranslationOptions public string AuthKey { get; set; } public string GlossaryById { get; set; } // glossary_id - public string GlossaryByName { get; set; } // glossary_id + public string GlossaryByName { get; set; } + public string TagHandling { get; set; } // tag_handling public decimal CostsPerCharacterInEUR { get; set; } = 20m / 1_000_000; diff --git a/text/Squidex.Text/Translations/DeepL/DeepLTranslationService.cs b/text/Squidex.Text/Translations/DeepL/DeepLTranslationService.cs index 895f3d8..457523f 100644 --- a/text/Squidex.Text/Translations/DeepL/DeepLTranslationService.cs +++ b/text/Squidex.Text/Translations/DeepL/DeepLTranslationService.cs @@ -151,6 +151,11 @@ public async Task> TranslateAsync(IEnumerable("glossary_id", options.GlossaryById)); } + if (!string.IsNullOrWhiteSpace(options.TagHandling)) + { + parameters.Add(new KeyValuePair("tag_handling", options.TagHandling)); + } + var url = options.AuthKey.EndsWith(":fx", StringComparison.Ordinal) ? UrlFree : From 51a83c9fe32745cb11e83f505f96c8ab0e2628f0 Mon Sep 17 00:00:00 2001 From: Stefan Sorgen Date: Wed, 18 Dec 2024 15:59:31 +0100 Subject: [PATCH 3/8] added debug tests --- .../ImageSharpThumbnailGenerator.cs | 15 +++++++++++++-- .../Squidex.Assets.ImageSharp.csproj | 3 ++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/assets/Squidex.Assets.ImageSharp/ImageSharpThumbnailGenerator.cs b/assets/Squidex.Assets.ImageSharp/ImageSharpThumbnailGenerator.cs index 980a4a9..54986ce 100644 --- a/assets/Squidex.Assets.ImageSharp/ImageSharpThumbnailGenerator.cs +++ b/assets/Squidex.Assets.ImageSharp/ImageSharpThumbnailGenerator.cs @@ -105,16 +105,27 @@ protected override async Task CreateThumbnailCoreAsync(Stream source, string mim image.Mutate(operation => { + if (options.Background != null && Color.TryParse(options.Background, out var color)) + { + File.AppendAllText(@"C:\DATA\Squidex.Assets.ImageSharp.log", "Use parsed color:" + color.ToString()); + operation.BackgroundColor(color); + } + else + { + File.AppendAllText(@"C:\DATA\Squidex.Assets.ImageSharp.log", "Use transparent:" + Color.Transparent.ToString()); + operation.BackgroundColor(Color.Transparent); + } + operation.Resize(resizeOptions); - if (options.Background != null && Color.TryParse(options.Background, out var color)) + /*if (options.Background != null && Color.TryParse(options.Background, out var color)) { operation.BackgroundColor(color); } else { operation.BackgroundColor(Color.Transparent); - } + }*/ }); } diff --git a/assets/Squidex.Assets.ImageSharp/Squidex.Assets.ImageSharp.csproj b/assets/Squidex.Assets.ImageSharp/Squidex.Assets.ImageSharp.csproj index 0e9685d..f961679 100644 --- a/assets/Squidex.Assets.ImageSharp/Squidex.Assets.ImageSharp.csproj +++ b/assets/Squidex.Assets.ImageSharp/Squidex.Assets.ImageSharp.csproj @@ -5,6 +5,7 @@ Latest enable Squidex.Assets + 5.18.0.2 @@ -22,7 +23,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + From 60c7a0aeef004e4606c179c090c119d144b54aeb Mon Sep 17 00:00:00 2001 From: Stefan Sorgen Date: Wed, 18 Dec 2024 19:07:20 +0100 Subject: [PATCH 4/8] added missing padcolor config to resize operation to handle background color properly --- .../ImageSharpThumbnailGenerator.cs | 24 +++---------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/assets/Squidex.Assets.ImageSharp/ImageSharpThumbnailGenerator.cs b/assets/Squidex.Assets.ImageSharp/ImageSharpThumbnailGenerator.cs index 54986ce..afa1ca4 100644 --- a/assets/Squidex.Assets.ImageSharp/ImageSharpThumbnailGenerator.cs +++ b/assets/Squidex.Assets.ImageSharp/ImageSharpThumbnailGenerator.cs @@ -93,7 +93,8 @@ protected override async Task CreateThumbnailCoreAsync(Stream source, string mim resizeMode = ISResizeMode.BoxPad; } - var resizeOptions = new ISResizeOptions { Size = new Size(w, h), Mode = resizeMode, PremultiplyAlpha = true }; + var bgColor = options.Background != null && Color.TryParse(options.Background, out var color) ? color : Color.Transparent; + var resizeOptions = new ISResizeOptions { Size = new Size(w, h), Mode = resizeMode, PremultiplyAlpha = true, PadColor = bgColor }; if (options.FocusX.HasValue && options.FocusY.HasValue) { @@ -105,27 +106,8 @@ protected override async Task CreateThumbnailCoreAsync(Stream source, string mim image.Mutate(operation => { - if (options.Background != null && Color.TryParse(options.Background, out var color)) - { - File.AppendAllText(@"C:\DATA\Squidex.Assets.ImageSharp.log", "Use parsed color:" + color.ToString()); - operation.BackgroundColor(color); - } - else - { - File.AppendAllText(@"C:\DATA\Squidex.Assets.ImageSharp.log", "Use transparent:" + Color.Transparent.ToString()); - operation.BackgroundColor(Color.Transparent); - } - operation.Resize(resizeOptions); - - /*if (options.Background != null && Color.TryParse(options.Background, out var color)) - { - operation.BackgroundColor(color); - } - else - { - operation.BackgroundColor(Color.Transparent); - }*/ + operation.BackgroundColor(bgColor); }); } From beb970329e99b5208d3a0d07bc5a1ceba27c27ec Mon Sep 17 00:00:00 2001 From: Stefan Sorgen Date: Mon, 31 Mar 2025 14:32:01 +0200 Subject: [PATCH 5/8] update image sharp library due vulnerability; see: https://github.com/advisories/GHSA-2cmq-823j-5qj8 --- .../Squidex.Assets.ImageSharp/Squidex.Assets.ImageSharp.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/Squidex.Assets.ImageSharp/Squidex.Assets.ImageSharp.csproj b/assets/Squidex.Assets.ImageSharp/Squidex.Assets.ImageSharp.csproj index da2848d..a3fbc1b 100644 --- a/assets/Squidex.Assets.ImageSharp/Squidex.Assets.ImageSharp.csproj +++ b/assets/Squidex.Assets.ImageSharp/Squidex.Assets.ImageSharp.csproj @@ -21,7 +21,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + From 993564a64f5d73d86ff37d757a5059a24631367d Mon Sep 17 00:00:00 2001 From: Stefan Sorgen Date: Mon, 31 Mar 2025 14:32:35 +0200 Subject: [PATCH 6/8] adapted client factory for glossary seek --- .../DeepL/DeepLTranslationService.cs | 54 ++++++++----------- 1 file changed, 23 insertions(+), 31 deletions(-) diff --git a/text/Squidex.Text/Translations/DeepL/DeepLTranslationService.cs b/text/Squidex.Text/Translations/DeepL/DeepLTranslationService.cs index 5e21eb7..e56da87 100644 --- a/text/Squidex.Text/Translations/DeepL/DeepLTranslationService.cs +++ b/text/Squidex.Text/Translations/DeepL/DeepLTranslationService.cs @@ -104,40 +104,38 @@ public async Task> TranslateAsync(IEnumerable("source_lang", GetLanguageCode(sourceLanguage))); } + var url = + options.AuthKey.EndsWith(":fx", StringComparison.Ordinal) ? + UrlFree : + UrlPaid; + + using var httpClient = CreateClient(); + if (!string.IsNullOrWhiteSpace(options.GlossaryByName) && string.IsNullOrWhiteSpace(options.GlossaryById)) { - var gl_url = - (options.AuthKey.EndsWith(":fx", StringComparison.Ordinal) ? - UrlFree : - UrlPaid).Replace("/translate", "/glossaries"); - - var gl_requestMessage = new HttpRequestMessage(HttpMethod.Get, gl_url); - gl_requestMessage.Headers.Add("Authorization", $"DeepL-Auth-Key {options.AuthKey}"); - var gl_httpClient = httpClientFactory.CreateClient("DeepL"); + var gl_url = url.Replace("/translate", "/glossaries"); + using var gl_httpRequest = new HttpRequestMessage(HttpMethod.Get, gl_url); + using var gl_httpResponse = await httpClient.SendAsync(gl_httpRequest, ct); - using (var gl_response = await gl_httpClient.SendAsync(gl_requestMessage, ct)) + try { - try - { - gl_response.EnsureSuccessStatusCode(); + gl_httpResponse.EnsureSuccessStatusCode(); - var jsonString = await gl_response.Content.ReadAsStringAsync(ct); - var jsonResponse = JsonSerializer.Deserialize(jsonString)!; + var gl_jsonString = await gl_httpResponse.Content.ReadAsStringAsync(ct); + var gl_jsonResponse = JsonSerializer.Deserialize(gl_jsonString)!; - var index = 0; - foreach (var glossary in jsonResponse.Glossaries) + foreach (var glossary in gl_jsonResponse.Glossaries) + { + if (options.GlossaryByName.Equals(glossary.Name, StringComparison.Ordinal)) { - if (options.GlossaryByName.Equals(glossary.Name, StringComparison.Ordinal)) - { - options.GlossaryById = glossary.GlossaryId; - break; - } + options.GlossaryById = glossary.GlossaryId; + break; } } - catch (Exception ex) - { - AddError(TranslationResult.Failed(ex)); - } + } + catch (Exception ex) + { + AddError(TranslationResult.Failed(ex)); } } @@ -151,12 +149,6 @@ public async Task> TranslateAsync(IEnumerable("tag_handling", options.TagHandling)); } - var url = - options.AuthKey.EndsWith(":fx", StringComparison.Ordinal) ? - UrlFree : - UrlPaid; - - using var httpClient = CreateClient(); using var httpRequest = new HttpRequestMessage(HttpMethod.Post, url) { Content = new FormUrlEncodedContent(parameters!), From 69f3a7778ed444ce9129159dc49205725ea190e8 Mon Sep 17 00:00:00 2001 From: Stefan Sorgen Date: Mon, 31 Mar 2025 14:38:40 +0200 Subject: [PATCH 7/8] fixed background color --- .../ImageSharpThumbnailGenerator.cs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/assets/Squidex.Assets.ImageSharp/ImageSharpThumbnailGenerator.cs b/assets/Squidex.Assets.ImageSharp/ImageSharpThumbnailGenerator.cs index cbb0934..033284e 100644 --- a/assets/Squidex.Assets.ImageSharp/ImageSharpThumbnailGenerator.cs +++ b/assets/Squidex.Assets.ImageSharp/ImageSharpThumbnailGenerator.cs @@ -77,7 +77,8 @@ protected override async Task CreateThumbnailCoreAsync(Stream source, string mim if (w > 0 || h > 0) { var resizeMode = GetResizeMode(options, w, h, image); - var resizeOptions = new ImageSharpOptions { Size = new Size(w, h), Mode = resizeMode, PremultiplyAlpha = true }; + var bgColor = options.Background != null && Color.TryParse(options.Background, out var color) ? color : Color.Transparent; + var resizeOptions = new ImageSharpOptions { Size = new Size(w, h), Mode = resizeMode, PremultiplyAlpha = true, PadColor = bgColor }; if (options.FocusX.HasValue && options.FocusY.HasValue) { @@ -89,14 +90,7 @@ protected override async Task CreateThumbnailCoreAsync(Stream source, string mim operation.Resize(resizeOptions); - if (options.Background != null && Color.TryParse(options.Background, out var color)) - { - operation.BackgroundColor(color); - } - else - { - operation.BackgroundColor(Color.Transparent); - } + operation.BackgroundColor(bgColor); } if (watermark != null) From bb66d57e50fae89b83f1a41ea6d36c285694775e Mon Sep 17 00:00:00 2001 From: Stefan Sorgen Date: Mon, 31 Mar 2025 14:40:31 +0200 Subject: [PATCH 8/8] DeepL: added tag_hanlding and glossary_id parameter for API; configurable by application.config; search for glossary-id by glossary-name (in case glossary will be updated frequently, id will change on each update) --- .../DeepL/DeepLTranslationOptions.cs | 4 ++ .../DeepL/DeepLTranslationService.cs | 69 +++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/text/Squidex.Text/Translations/DeepL/DeepLTranslationOptions.cs b/text/Squidex.Text/Translations/DeepL/DeepLTranslationOptions.cs index eb6f27c..9fce57d 100644 --- a/text/Squidex.Text/Translations/DeepL/DeepLTranslationOptions.cs +++ b/text/Squidex.Text/Translations/DeepL/DeepLTranslationOptions.cs @@ -11,6 +11,10 @@ public sealed class DeepLTranslationOptions { public string AuthKey { get; set; } + public string GlossaryById { get; set; } // glossary_id + public string GlossaryByName { get; set; } + public string TagHandling { get; set; } // tag_handling + public decimal CostsPerCharacterInEUR { get; set; } = 20m / 1_000_000; public Dictionary Mapping { get; set; } diff --git a/text/Squidex.Text/Translations/DeepL/DeepLTranslationService.cs b/text/Squidex.Text/Translations/DeepL/DeepLTranslationService.cs index cd0fc50..e56da87 100644 --- a/text/Squidex.Text/Translations/DeepL/DeepLTranslationService.cs +++ b/text/Squidex.Text/Translations/DeepL/DeepLTranslationService.cs @@ -35,6 +35,36 @@ private sealed class TranslationDto public string DetectedSourceLanguage { get; set; } } + private sealed class GlossariesDto + { + [JsonPropertyName("glossaries")] + public GlossaryDto[] Glossaries { get; set; } + } + + private sealed class GlossaryDto + { + [JsonPropertyName("glossary_id")] + public string GlossaryId { get; set; } // "def3a26b-3e84-45b3-84ae-0c0aaf3525f7" + + [JsonPropertyName("name")] + public string Name { get; set; } // "My Glossary" + + [JsonPropertyName("ready")] + public bool Ready { get; set; } // true + + [JsonPropertyName("source_lang")] + public string SourceLang { get; set; } // "EN" + + [JsonPropertyName("target_lang")] + public string TargetLang { get; set; } // "DE" + + [JsonPropertyName("creation_time")] + public DateTime CreationTime { get; set; } // "2021-08-03T14:16:18.329Z" + + [JsonPropertyName("entry_count")] + public int EntryCount { get; set; } // 1 + } + public bool IsConfigured { get; } = !string.IsNullOrWhiteSpace(options.Value.AuthKey); public async Task> TranslateAsync(IEnumerable texts, string targetLanguage, string? sourceLanguage = null, @@ -80,6 +110,45 @@ public async Task> TranslateAsync(IEnumerable(gl_jsonString)!; + + foreach (var glossary in gl_jsonResponse.Glossaries) + { + if (options.GlossaryByName.Equals(glossary.Name, StringComparison.Ordinal)) + { + options.GlossaryById = glossary.GlossaryId; + break; + } + } + } + catch (Exception ex) + { + AddError(TranslationResult.Failed(ex)); + } + } + + if (!string.IsNullOrWhiteSpace(options.GlossaryById)) + { + parameters.Add(new KeyValuePair("glossary_id", options.GlossaryById)); + } + + if (!string.IsNullOrWhiteSpace(options.TagHandling)) + { + parameters.Add(new KeyValuePair("tag_handling", options.TagHandling)); + } + using var httpRequest = new HttpRequestMessage(HttpMethod.Post, url) { Content = new FormUrlEncodedContent(parameters!),