From 55bab1d707ba23e65804769f8da4fd49c6c63801 Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Mon, 25 May 2020 20:03:45 +0800 Subject: [PATCH] Add cli translate command. Resolve #4072 --- .../Volo/Abp/Cli/AbpCliCoreModule.cs | 7 +- .../Volo/Abp/Cli/Commands/TranslateCommand.cs | 292 ++++++++++++++++++ modules/identity/abp-translation.json | 33 ++ 3 files changed, 329 insertions(+), 3 deletions(-) create mode 100644 framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/TranslateCommand.cs create mode 100644 modules/identity/abp-translation.json diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs index f5a5a44980..dbffa820c8 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs @@ -28,11 +28,12 @@ namespace Volo.Abp.Cli options.Commands["add-package"] = typeof(AddPackageCommand); options.Commands["add-module"] = typeof(AddModuleCommand); options.Commands["login"] = typeof(LoginCommand); - options.Commands["logout"] = typeof(LogoutCommand); - options.Commands["generate-proxy"] = typeof(GenerateProxyCommand); + options.Commands["logout"] = typeof(LogoutCommand); + options.Commands["generate-proxy"] = typeof(GenerateProxyCommand); options.Commands["suite"] = typeof(SuiteCommand); options.Commands["switch-to-preview"] = typeof(SwitchNightlyPreviewCommand); - options.Commands["switch-to-stable"] = typeof(SwitchStableCommand); + options.Commands["switch-to-stable"] = typeof(SwitchStableCommand); + options.Commands["translate"] = typeof(TranslateCommand); }); } } 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 new file mode 100644 index 0000000000..14d5493ad7 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/TranslateCommand.cs @@ -0,0 +1,292 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Volo.Abp.Cli.Args; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Cli.Commands +{ + public class TranslateCommand: IConsoleCommand, ITransientDependency + { + public ILogger Logger { get; set; } + + public Task ExecuteAsync(CommandLineArgs commandLineArgs) + { + var currentDirectory = @"D:\Github\volo\abp\modules\identity"; //Directory.GetCurrentDirectory(); + + 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); + if (referenceCulture == null) + { + throw new CliUsageException( + "reference culture is missing!" + + Environment.NewLine + Environment.NewLine + + GetUsageInfo() + ); + } + + var outputFile = Path.Combine(currentDirectory, + commandLineArgs.Options.GetOrNull(Options.Output.Short, Options.Output.Long) + ?? "abp-translation.json"); + + var allValues = Convert.ToBoolean(commandLineArgs.Options.GetOrNull(Options.AllValues.Short, Options.AllValues.Long) + ?? "false"); + + 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 to {outputFile}."); + + return Task.CompletedTask; + } + + private static AbpTranslateInfo GetAbpTranslateInfo(string directory, string targetCultureName, string referenceCultureName, bool allValues) + { + var translateInfo = new AbpTranslateInfo + { + ReferenceCulture = referenceCultureName, + TargetCulture = targetCultureName, + Resources = new List() + }; + + var referenceCultureFiles = GetCultureJsonFiles(directory, referenceCultureName); + foreach (var filePath in referenceCultureFiles) + { + var directoryName = Path.GetDirectoryName(filePath) ?? string.Empty; + + var referenceLocalizationInfo = GetAbpLocalizationInfoOrNull(filePath); + if (referenceLocalizationInfo == null) // Not abp json file + { + continue; + } + + var resource = new AbpTranslateResource + { + ResourcePath = directoryName, + Texts = new List() + }; + + foreach (var text in referenceLocalizationInfo.Texts) + { + resource.Texts.Add(new AbpTranslateResourceText + { + LocalizationKey = text.Name, + Reference = text.Value, + Target = string.Empty + }); + } + + //Use target json file content to fill resource texts + var targetFile = Path.Combine(directoryName, $"{targetCultureName}.json"); + if (File.Exists(targetFile)) + { + var targetLocalizationInfo = GetAbpLocalizationInfoOrNull(targetFile); + foreach (var referenceResourceText in resource.Texts) + { + var text = targetLocalizationInfo.Texts.FirstOrDefault(x => x.Name == referenceResourceText.LocalizationKey); + referenceResourceText.Target = text?.Value ?? string.Empty; + } + } + + if (!allValues) + { + //Only include missing keys. + resource.Texts.RemoveAll(x => !x.Target.Equals(string.Empty)); + } + + if (resource.Texts.Any()) + { + translateInfo.Resources.Add(resource); + } + } + + return translateInfo; + } + + private static IEnumerable GetCultureJsonFiles(string directory,string cultureName) + { + var excludeDirectory = new List() + { + "node_modules", + Path.Combine("bin", "debug"), + Path.Combine("obj", "debug") + }; + + var allCultureInfos = CultureInfo.GetCultures(CultureTypes.AllCultures); + + return Directory.GetFiles(directory, "*.json", SearchOption.AllDirectories) + .Where(file => excludeDirectory.All(x => file.IndexOf(x, StringComparison.OrdinalIgnoreCase) == -1)) + .Where(jsonFile => allCultureInfos.Any(culture => jsonFile.EndsWith($"{cultureName}.json", StringComparison.OrdinalIgnoreCase))); + } + + private static AbpLocalizationInfo GetAbpLocalizationInfoOrNull(string path) + { + var json = File.ReadAllText(path); + JObject jObject; + try + { + jObject = JObject.Parse(json); + } + catch (Exception e) + { + return null; + } + + var culture = jObject.GetValue("culture"); + var texts = jObject.GetValue("texts"); + if (culture == null || texts == null) + { + return null; + } + + var localizationInfo = new AbpLocalizationInfo + { + Culture = culture.Value(), + Texts = new List() + }; + + if (texts.Any()) + { + foreach (var text in texts) + { + var property = (text as JProperty); + localizationInfo.Texts.Add(new NameValue(property?.Name, property?.Value.Value())); + } + } + + return localizationInfo; + } + + public string GetUsageInfo() + { + var sb = new StringBuilder(); + + sb.AppendLine(""); + sb.AppendLine("Usage:"); + sb.AppendLine(""); + sb.AppendLine(" abp translate [options]"); + sb.AppendLine(""); + sb.AppendLine("Options:"); + sb.AppendLine(""); + sb.AppendLine("--culture|-c Target culture. eg: zh-Hans"); + sb.AppendLine("--reference-culture|-r Default: en)"); + sb.AppendLine("--output|-o Output file name"); + sb.AppendLine("--all-values|-all Include all keys"); + sb.AppendLine("--apply|-a Creates or updates the file for the translated culture."); + sb.AppendLine("--file|-f Default: abp-translation.json"); + sb.AppendLine(""); + sb.AppendLine("Examples:"); + sb.AppendLine(""); + sb.AppendLine(" abp translate -c zh-Hans -r en"); + sb.AppendLine(" abp translate -c zh-Hans -r en -a"); + sb.AppendLine(" abp translate apply"); + sb.AppendLine(" abp translate apply -f my-translation.json"); + sb.AppendLine(""); + sb.AppendLine("See the documentation for more info: https://docs.abp.io/en/abp/latest/CLI"); + + return sb.ToString(); + } + + public string GetShortDescription() + { + return "Mainly used to translate ABP's resources (JSON files) easier."; + } + + public static class Options + { + public static class Culture + { + public const string Short = "c"; + public const string Long = "culture"; + } + + public static class ReferenceCulture + { + public const string Short = "r"; + public const string Long = "reference-culture"; + } + + public static class Output + { + public const string Short = "o"; + public const string Long = "output"; + } + + public static class AllValues + { + public const string Short = "all"; + public const string Long = "all-values"; + } + + public static class Apply + { + public const string Short = "a"; + public const string Long = "apply"; + } + + public static class File + { + public const string Short = "f"; + public const string Long = "file"; + } + } + + public class AbpTranslateInfo + { + public string ReferenceCulture { get; set; } + + public string TargetCulture { get; set; } + + public List Resources { get; set; } + } + + public class AbpTranslateResource + { + public string ResourcePath { get; set; } + + public List Texts { get; set; } + } + + public class AbpTranslateResourceText + { + public string LocalizationKey { get; set; } + + public string Reference { get; set; } + + public string Target { get; set; } + } + + public class AbpLocalizationInfo + { + public string Culture { get; set; } + + public List Texts { get; set; } + } + } +} diff --git a/modules/identity/abp-translation.json b/modules/identity/abp-translation.json new file mode 100644 index 0000000000..7bca8c8ca3 --- /dev/null +++ b/modules/identity/abp-translation.json @@ -0,0 +1,33 @@ +// test + +{ + "ReferenceCulture": "en", + "TargetCulture": "zh-Hans", + "Resources": [ + { + "ResourcePath": "D:\\Github\\volo\\abp\\modules\\identity\\src\\Volo.Abp.Identity.Domain.Shared\\Volo\\Abp\\Identity\\Localization", + "Texts": [ + { + "LocalizationKey": "Identity.OrganizationUnit.MaxUserMembershipCount", + "Reference": "Maximum allowed organization unit membership count for a user", + "Target": "" + }, + { + "LocalizationKey": "DisplayName:Abp.Identity.SignIn.EnablePhoneNumberConfirmation", + "Reference": "Enable phone number confirmation", + "Target": "" + }, + { + "LocalizationKey": "Description:Abp.Identity.SignIn.EnablePhoneNumberConfirmation", + "Reference": "Whether the phoneNumber can be confirmed by the user.", + "Target": "" + }, + { + "LocalizationKey": "Volo.Abp.Identity:010002", + "Reference": "Can not set more than {MaxUserMembershipCount} organization unit for a user!", + "Target": "" + } + ] + } + ] +} \ No newline at end of file