From 2c71729469e6cf1929bf60d606091d6c26eb51ab Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 29 Dec 2023 14:39:01 +0800 Subject: [PATCH 1/5] Add `Deepl` online translate to CLI. --- Directory.Packages.props | 1 + .../Volo.Abp.Cli.Core.csproj | 1 + .../Volo/Abp/Cli/Commands/TranslateCommand.cs | 247 ++++++++++++++---- 3 files changed, 204 insertions(+), 45 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 50c0f4d7f2..a2a5579929 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -29,6 +29,7 @@ + diff --git a/framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj b/framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj index 15f7406f76..589509bd22 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj +++ b/framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj @@ -24,6 +24,7 @@ + diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/TranslateCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/TranslateCommand.cs index ea6b1d45f6..8346153dd4 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/TranslateCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/TranslateCommand.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; +using DeepL; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -16,64 +17,61 @@ namespace Volo.Abp.Cli.Commands; public class TranslateCommand : IConsoleCommand, ITransientDependency { public const string Name = "translate"; - + public ILogger Logger { get; set; } - public Task ExecuteAsync(CommandLineArgs commandLineArgs) + public async Task ExecuteAsync(CommandLineArgs commandLineArgs) { var currentDirectory = Directory.GetCurrentDirectory(); + var referenceCulture = commandLineArgs.Options.GetOrNull(Options.ReferenceCulture.Short, Options.ReferenceCulture.Long) ?? "en"; + var allValues = commandLineArgs.Options.ContainsKey(Options.AllValues.Short) || commandLineArgs.Options.ContainsKey(Options.AllValues.Long); - var apply = commandLineArgs.Options.ContainsKey(Options.Apply.Short) || commandLineArgs.Options.ContainsKey(Options.Apply.Long); - if (apply) + // Apply abp-translation.json file + if (commandLineArgs.Options.ContainsKey(Options.Apply.Short) || commandLineArgs.Options.ContainsKey(Options.Apply.Long)) { - var inputFile = Path.Combine(currentDirectory, - commandLineArgs.Options.GetOrNull(Options.File.Short, Options.File.Long) - ?? "abp-translation.json"); - - Logger.LogInformation("Abp translate apply..."); - Logger.LogInformation("Input file: " + inputFile); - - ApplyAbpTranslateInfo(currentDirectory, inputFile); + var inputFile = Path.Combine(currentDirectory, commandLineArgs.Options.GetOrNull(Options.File.Short, Options.File.Long) ?? "abp-translation.json"); + await ApplyAbpTranslateInfoAsync(currentDirectory, inputFile); + return; } - else - { - var targetCulture = commandLineArgs.Options.GetOrNull(Options.Culture.Short, Options.Culture.Long); - if (targetCulture == null) - { - throw new CliUsageException( - "Target culture is missing!" + - Environment.NewLine + Environment.NewLine + - GetUsageInfo() - ); - } - - var referenceCulture = commandLineArgs.Options.GetOrNull(Options.ReferenceCulture.Short, Options.ReferenceCulture.Long) - ?? "en"; - - var outputFile = Path.Combine(currentDirectory, - commandLineArgs.Options.GetOrNull(Options.Output.Short, Options.Output.Long) - ?? "abp-translation.json"); - var allValues = commandLineArgs.Options.ContainsKey(Options.AllValues.Short) || - commandLineArgs.Options.ContainsKey(Options.AllValues.Long); - - Logger.LogInformation("Abp translate..."); - Logger.LogInformation("Target culture: " + targetCulture); - Logger.LogInformation("Reference culture: " + referenceCulture); - Logger.LogInformation("Output file: " + outputFile); + var targetCulture = commandLineArgs.Options.GetOrNull(Options.Culture.Short, Options.Culture.Long); + if (targetCulture == null) + { + throw new CliUsageException("Target culture is missing!" + Environment.NewLine + Environment.NewLine + GetUsageInfo()); + } - if (allValues) + // Translate online + if (commandLineArgs.Options.ContainsKey(Options.Online.Long)) + { + var authKey = commandLineArgs.Options.GetOrNull(Options.DeepLAuthKey.Short, Options.DeepLAuthKey.Short); + if (authKey == null) { - Logger.LogInformation("Include all keys"); + throw new CliUsageException("DeepL auth key is missing!" + Environment.NewLine + Environment.NewLine + GetUsageInfo()); } + await TranslateAbpTranslateInfoAsync(currentDirectory, targetCulture, referenceCulture, allValues, authKey); + return; + } - var translateInfo = GetAbpTranslateInfo(currentDirectory, targetCulture, referenceCulture, allValues); - - File.WriteAllText(outputFile, JsonConvert.SerializeObject(translateInfo, Formatting.Indented)); + // Generate abp-translation.json file + var outputFile = Path.Combine(currentDirectory, commandLineArgs.Options.GetOrNull(Options.Output.Short, Options.Output.Long) ?? "abp-translation.json"); + await GenerateAbpTranslateInfoAsync(currentDirectory, targetCulture, referenceCulture, allValues, outputFile); + } - Logger.LogInformation($"The translation file has been created."); + private Task GenerateAbpTranslateInfoAsync(string currentDirectory, string targetCulture, string referenceCulture, bool allValues, string outputFile) + { + Logger.LogInformation("Abp translate..."); + Logger.LogInformation("Target culture: " + targetCulture); + Logger.LogInformation("Reference culture: " + referenceCulture); + Logger.LogInformation("Output file: " + outputFile); + if (allValues) + { + Logger.LogInformation("Include all keys"); } + var translateInfo = GetAbpTranslateInfo(currentDirectory, targetCulture, referenceCulture, allValues); + File.WriteAllText(outputFile, JsonConvert.SerializeObject(translateInfo, Formatting.Indented)); + Logger.LogInformation($"The translation file has been created."); + return Task.CompletedTask; } @@ -140,8 +138,151 @@ public class TranslateCommand : IConsoleCommand, ITransientDependency return translateInfo; } - private void ApplyAbpTranslateInfo(string directory, string filename) + private async Task TranslateAbpTranslateInfoAsync(string directory, string targetCulture, string referenceCulture, bool allValues, string authKey) + { + Logger.LogInformation("Abp translate online..."); + Logger.LogInformation("Target culture: " + targetCulture); + Logger.LogInformation("Reference culture: " + referenceCulture); + if (allValues) + { + Logger.LogInformation("Include all keys"); + } + + targetCulture = await GetDeeplLanguageCode(targetCulture); + referenceCulture = await GetDeeplLanguageCode(referenceCulture); + var translateInfo = GetAbpTranslateInfo(directory, targetCulture, referenceCulture, allValues); + foreach (var resource in translateInfo.Resources) + { + var targetFile = Path.Combine(resource.ResourcePath, translateInfo.TargetCulture + ".json"); + var targetLocalizationInfo = File.Exists(targetFile) + ? GetAbpLocalizationInfoOrNull(targetFile) + : new AbpLocalizationInfo() + { + Culture = translateInfo.TargetCulture, + Texts = new List() + }; + + if (targetLocalizationInfo == null) + { + throw new CliUsageException( + $"Failed to get localization information from {targetFile} file." + + Environment.NewLine + Environment.NewLine + + GetUsageInfo() + ); + } + + var referenceFile = Path.Combine(resource.ResourcePath, translateInfo.ReferenceCulture + ".json"); + if (!File.Exists(referenceFile)) + { + throw new CliUsageException( + $"{referenceFile} file does not exist.." + + Environment.NewLine + Environment.NewLine + + GetUsageInfo() + ); + } + var referenceLocalizationInfo = GetAbpLocalizationInfoOrNull(referenceFile); + if (referenceLocalizationInfo == null) + { + throw new CliUsageException( + $"Failed to get localization information from {referenceFile} file." + + Environment.NewLine + Environment.NewLine + + GetUsageInfo() + ); + } + + var translator = new Translator(authKey); + + var texts = resource.Texts.Select(x => x.Reference); + + var translations = await translator.TranslateTextAsync(texts, referenceCulture, targetCulture); + for (var i = 0; i < translations.Length; i++) + { + resource.Texts[i].Target = translations[i].Text; + } + + foreach (var text in resource.Texts) + { + var targetText = targetLocalizationInfo.Texts.FirstOrDefault(x => x.Name == text.LocalizationKey); + if (targetText != null) + { + if (!text.Target.IsNullOrEmpty()) + { + Logger.LogInformation($"Update translation: {targetText.Name} => " + text.Target); + targetText.Value = text.Target; + } + } + else + { + Logger.LogInformation($"Create translation: {text.LocalizationKey} => " + text.Target); + targetLocalizationInfo.Texts.Add(new NameValue(text.LocalizationKey, text.Target)); + } + } + + Logger.LogInformation($"Write translation json to {targetFile}."); + + // sort keys + targetLocalizationInfo = SortLocalizedKeys(targetLocalizationInfo, referenceLocalizationInfo); + File.WriteAllText(targetFile, AbpLocalizationInfoToJsonFile(targetLocalizationInfo)); + } + } + + private Task GetDeeplLanguageCode(string abpCulture) { + var deeplLanguages = new List() + { + LanguageCode.Bulgarian , + LanguageCode.Czech , + LanguageCode.Danish , + LanguageCode.German , + LanguageCode.Greek , + LanguageCode.English , + LanguageCode.EnglishBritish , + LanguageCode.EnglishAmerican , + LanguageCode.Spanish , + LanguageCode.Estonian , + LanguageCode.Finnish , + LanguageCode.French , + LanguageCode.Hungarian , + LanguageCode.Indonesian , + LanguageCode.Italian , + LanguageCode.Japanese , + LanguageCode.Korean , + LanguageCode.Lithuanian , + LanguageCode.Latvian , + LanguageCode.Norwegian , + LanguageCode.Dutch , + LanguageCode.Polish , + LanguageCode.Portuguese , + LanguageCode.PortugueseBrazilian , + LanguageCode.PortugueseEuropean , + LanguageCode.Romanian , + LanguageCode.Russian , + LanguageCode.Slovak , + LanguageCode.Slovenian , + LanguageCode.Swedish , + LanguageCode.Turkish , + LanguageCode.Ukrainian, + LanguageCode.Chinese + }; + + var deeplCulture = deeplLanguages.FirstOrDefault(x => x.Equals(abpCulture, StringComparison.OrdinalIgnoreCase)); + if (deeplCulture == null) + { + throw new CliUsageException( + $"DeepL does not support {abpCulture} culture." + + Environment.NewLine + Environment.NewLine + + GetUsageInfo() + ); + } + + return Task.FromResult(deeplCulture); + } + + private Task ApplyAbpTranslateInfoAsync(string directory, string filename) + { + Logger.LogInformation("Abp translate apply..."); + Logger.LogInformation("Input file: " + filename); + var translateJsonPath = Path.Combine(directory, filename); if (!File.Exists(translateJsonPath)) { @@ -220,6 +361,8 @@ public class TranslateCommand : IConsoleCommand, ITransientDependency File.Delete(translateJsonPath); Logger.LogInformation($"Delete the {translateJsonPath} file, if you need to translate again, please re-run the [abp translate] command."); } + + return Task.CompletedTask; } private static IEnumerable GetCultureJsonFiles(string path, string cultureName) @@ -357,13 +500,17 @@ public class TranslateCommand : IConsoleCommand, ITransientDependency sb.AppendLine("--all-values|-all Include all keys. Default false"); sb.AppendLine("--apply|-a Creates or updates the file for the translated culture."); sb.AppendLine("--file|-f Default: abp-translation.json"); + sb.AppendLine("--online|-o Translate online."); + sb.AppendLine("--deepl-auth-key DeepL auth key for online translation."); sb.AppendLine(""); sb.AppendLine("Examples:"); sb.AppendLine(""); sb.AppendLine(" abp translate -c zh-Hans"); - sb.AppendLine(" abp translate -c zh-Hans -r en -a"); + sb.AppendLine(" abp translate -c zh-Hans -r en"); sb.AppendLine(" abp translate --apply"); sb.AppendLine(" abp translate -a -f my-translation.json"); + sb.AppendLine(" abp translate -c zh-Hans --deepl-auth-key "); + sb.AppendLine(" abp translate -c zh-Hans -r tr --online --deepl-auth-key "); sb.AppendLine(""); sb.AppendLine("See the documentation for more info: https://docs.abp.io/en/abp/latest/CLI"); @@ -412,6 +559,16 @@ public class TranslateCommand : IConsoleCommand, ITransientDependency public const string Short = "f"; public const string Long = "file"; } + + public static class Online + { + public const string Long = "online"; + } + + public static class DeepLAuthKey + { + public const string Short = "deepl-auth-key"; + } } public class AbpTranslateInfo From 7d0fd367157b5f65ca1b8b59cc20430f1bf4af80 Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 29 Dec 2023 14:47:24 +0800 Subject: [PATCH 2/5] Update TranslateCommand.cs --- .../Volo/Abp/Cli/Commands/TranslateCommand.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/TranslateCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/TranslateCommand.cs index 8346153dd4..09b5300c84 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/TranslateCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/TranslateCommand.cs @@ -148,8 +148,6 @@ public class TranslateCommand : IConsoleCommand, ITransientDependency Logger.LogInformation("Include all keys"); } - targetCulture = await GetDeeplLanguageCode(targetCulture); - referenceCulture = await GetDeeplLanguageCode(referenceCulture); var translateInfo = GetAbpTranslateInfo(directory, targetCulture, referenceCulture, allValues); foreach (var resource in translateInfo.Resources) { @@ -194,7 +192,7 @@ public class TranslateCommand : IConsoleCommand, ITransientDependency var texts = resource.Texts.Select(x => x.Reference); - var translations = await translator.TranslateTextAsync(texts, referenceCulture, targetCulture); + var translations = await translator.TranslateTextAsync(texts, await GetDeeplLanguageCode(referenceCulture), await GetDeeplLanguageCode(targetCulture)); for (var i = 0; i < translations.Length; i++) { resource.Texts[i].Target = translations[i].Text; @@ -265,6 +263,11 @@ public class TranslateCommand : IConsoleCommand, ITransientDependency LanguageCode.Chinese }; + if (abpCulture == "zh-Hans") + { + return Task.FromResult(LanguageCode.Chinese); + } + var deeplCulture = deeplLanguages.FirstOrDefault(x => x.Equals(abpCulture, StringComparison.OrdinalIgnoreCase)); if (deeplCulture == null) { From 1b8deef2ed86fa33d61ea1a9d4161c35d2e68a21 Mon Sep 17 00:00:00 2001 From: maliming Date: Sat, 30 Dec 2023 21:37:09 +0800 Subject: [PATCH 3/5] Update CLI.md --- docs/en/CLI.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/en/CLI.md b/docs/en/CLI.md index 48858edc74..2542e6488e 100644 --- a/docs/en/CLI.md +++ b/docs/en/CLI.md @@ -527,6 +527,15 @@ Then review changes on your source control system to be sure that it has changed * `--file` or `-f`: Default: `abp-translation.json`. The translation file (use only if you've used the `--output` option before). +#### Online DeepL translate + +The translation command also supports online translation. You need to provide your DeepL [Authentication Key](https://support.deepl.com/hc/en-us/articles/360020695820-Authentication-Key). + +````bash +abp translate -c zh-Hans --online --deepl-auth-key "); +abp translate -c zh-Hans -r tr --online --deepl-auth-key "); +````bash + ### login Some features of the CLI requires to be logged in to abp.io platform. To login with your username write: From cd982112608af18b109df354308c3c9c891e368d Mon Sep 17 00:00:00 2001 From: maliming Date: Sat, 30 Dec 2023 21:37:48 +0800 Subject: [PATCH 4/5] Update TranslateCommand.cs --- .../Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/TranslateCommand.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/TranslateCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/TranslateCommand.cs index 09b5300c84..73b6a74421 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/TranslateCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/TranslateCommand.cs @@ -512,7 +512,7 @@ public class TranslateCommand : IConsoleCommand, ITransientDependency sb.AppendLine(" abp translate -c zh-Hans -r en"); sb.AppendLine(" abp translate --apply"); sb.AppendLine(" abp translate -a -f my-translation.json"); - sb.AppendLine(" abp translate -c zh-Hans --deepl-auth-key "); + sb.AppendLine(" abp translate -c zh-Hans --online --deepl-auth-key "); sb.AppendLine(" abp translate -c zh-Hans -r tr --online --deepl-auth-key "); sb.AppendLine(""); sb.AppendLine("See the documentation for more info: https://docs.abp.io/en/abp/latest/CLI"); From 2585378c41afa85068399cc05fd4ef140bd18e5e Mon Sep 17 00:00:00 2001 From: maliming Date: Sat, 30 Dec 2023 21:42:39 +0800 Subject: [PATCH 5/5] Update CLI.md --- docs/en/CLI.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/en/CLI.md b/docs/en/CLI.md index 2542e6488e..8c1b320861 100644 --- a/docs/en/CLI.md +++ b/docs/en/CLI.md @@ -529,12 +529,13 @@ Then review changes on your source control system to be sure that it has changed #### Online DeepL translate -The translation command also supports online translation. You need to provide your DeepL [Authentication Key](https://support.deepl.com/hc/en-us/articles/360020695820-Authentication-Key). +The `translate` command also supports online translation. You need to provide your [DeepL Authentication Key](https://support.deepl.com/hc/en-us/articles/360020695820-Authentication-Key). + +It will search all the `en.json(reference-culture)` files in the directory and sub-directory and then translate and generate the corresponding `zh-Hans.json(culture)` files. ````bash -abp translate -c zh-Hans --online --deepl-auth-key "); -abp translate -c zh-Hans -r tr --online --deepl-auth-key "); -````bash +abp translate -c zh-Hans --online --deepl-auth-key +```` ### login