From 2c71729469e6cf1929bf60d606091d6c26eb51ab Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 29 Dec 2023 14:39:01 +0800 Subject: [PATCH] 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