diff --git a/docs/en/Community-Articles/2023-02-22-Streamline-Localization-In-Your-ABP-Project/POST.md b/docs/en/Community-Articles/2023-02-22-Streamline-Localization-In-Your-ABP-Project/POST.md new file mode 100644 index 0000000000..63936521c6 --- /dev/null +++ b/docs/en/Community-Articles/2023-02-22-Streamline-Localization-In-Your-ABP-Project/POST.md @@ -0,0 +1,44 @@ +## Streamline Localization in Your ABP Project + +Making localization changes to an ABP project can be a daunting task, especially if you're dealing with multiple languages and translations. During development, it's easy to overlook some changes and that leads to inconsistencies across different languages. Fortunately, I have developed a tool that can help streamline the localization process and ensure consistency across different languages. + +The tool is a console application that uses JSON files to manage localization keys and their translations. It addresses three common scenarios that can arise during localization: + + +1. When the argument count of a key changes, it can be difficult to update the translations for all languages. My tool solves this problem by scanning all JSON files in the project folder and identifying any keys that have mismatched argument counts. It then offers two options to the user: delete the mismatched translations or export them as a JSON file for manual editing. + +2. When a new key is added to the project, forgetting to add its translations to all the other languages is easy. My tool helps to avoid this issue by scanning the default language's JSON file and identifying any keys that don't have translations in other languages. It then exports these keys as a JSON file that can be used to add missing translations. + +3. When a key's name is changed, it's important to update its translations in all the other languages. My tool makes this task simple by scanning all the JSON files in the project folder and updating any translations of the old key name with the new one. + +The tool also includes an export feature that allows users to modify translations outside of the application and import them back into the JSON files. + +## How it Helps + +With my Localization Key Synchronizer tool, you can perform complex localization changes more quickly and easily than by manually sifting through files and making changes one-by-one. This can save you significant time and effort, especially if you're working with a large number of languages or translations. + +## How it Works + +When you run the Localization Key Synchronizer tool, it presents you with three options: + +1. Find Asynchronous Keys +2. Apply Changes in the Exported File +3. Replace Keys + +If you select "Find Asynchronous Keys," the tool prompts you to enter the default language path. Once you've entered the path, the tool displays all of the JSON files in the same folder as a multi-select list. After selecting one or more files, you are asked whether you want to find keys that do not match the number of arguments, missing keys, or both. If you select "Missing Keys," the tool prompts you to enter the absolute path to export the missing keys. After you've entered the path, the export process starts, and the tool closes. + +![](./images/Part1.gif) + +If you select "Apply Changes in the Exported File" at the main menu, the tool prompts you to enter the path to the exported file. After you've entered the path, the import process starts, and the tool closes. + +![](./images/Part2.gif) + +If you select "Replace Keys," the tool prompts you to enter the localization folder path, the old key, the new key, and the JSON files to apply the changes to. Once you've entered all the required information and made your selections, the tool performs the replacements and closes. + +![](./images/Part3.gif) + +## Conclusion + +If you're struggling to manage localization changes in an ABP project, give my Localization Key Synchronizer tool a try. It can help streamline your workflow and make the process much more manageable. You can find the tool on [GitHub](https://github.com/abpframework/abp/tree/dev/tools/localization-key-synchronizer). + +To use the tool, simply run the console application and follow the prompts. It's a user-friendly solution that helps to ensure localization consistency in your ABP project. Give it a try and let me know what you think! diff --git a/docs/en/Community-Articles/2023-02-22-Streamline-Localization-In-Your-ABP-Project/images/Part1.gif b/docs/en/Community-Articles/2023-02-22-Streamline-Localization-In-Your-ABP-Project/images/Part1.gif new file mode 100644 index 0000000000..f2e6b66e64 Binary files /dev/null and b/docs/en/Community-Articles/2023-02-22-Streamline-Localization-In-Your-ABP-Project/images/Part1.gif differ diff --git a/docs/en/Community-Articles/2023-02-22-Streamline-Localization-In-Your-ABP-Project/images/Part2.gif b/docs/en/Community-Articles/2023-02-22-Streamline-Localization-In-Your-ABP-Project/images/Part2.gif new file mode 100644 index 0000000000..9bc70d361d Binary files /dev/null and b/docs/en/Community-Articles/2023-02-22-Streamline-Localization-In-Your-ABP-Project/images/Part2.gif differ diff --git a/docs/en/Community-Articles/2023-02-22-Streamline-Localization-In-Your-ABP-Project/images/Part3.gif b/docs/en/Community-Articles/2023-02-22-Streamline-Localization-In-Your-ABP-Project/images/Part3.gif new file mode 100644 index 0000000000..8a8ae32d25 Binary files /dev/null and b/docs/en/Community-Articles/2023-02-22-Streamline-Localization-In-Your-ABP-Project/images/Part3.gif differ diff --git a/tools/localization-key-synchronizer/LocalizationKeySynchronizer.exe b/tools/localization-key-synchronizer/LocalizationKeySynchronizer.exe new file mode 100644 index 0000000000..c8675a0a67 Binary files /dev/null and b/tools/localization-key-synchronizer/LocalizationKeySynchronizer.exe differ diff --git a/tools/localization-key-synchronizer/LocalizationKeySynchronizer.sln b/tools/localization-key-synchronizer/LocalizationKeySynchronizer.sln new file mode 100644 index 0000000000..a9a9ba0988 --- /dev/null +++ b/tools/localization-key-synchronizer/LocalizationKeySynchronizer.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LocalizationKeySynchronizer", "src\LocalizationKeySynchronizer.csproj", "{FFD8DF5E-2724-4D15-9C84-7ACFEEF6F270}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {FFD8DF5E-2724-4D15-9C84-7ACFEEF6F270}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FFD8DF5E-2724-4D15-9C84-7ACFEEF6F270}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FFD8DF5E-2724-4D15-9C84-7ACFEEF6F270}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FFD8DF5E-2724-4D15-9C84-7ACFEEF6F270}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/tools/localization-key-synchronizer/README.md b/tools/localization-key-synchronizer/README.md new file mode 100644 index 0000000000..f2b9401a30 --- /dev/null +++ b/tools/localization-key-synchronizer/README.md @@ -0,0 +1,27 @@ +# Streamline Localization in Your ABP Project with this Console Tool + +Are you tired of manually managing your localization files for your software projects? Look no further than our new console application designed to streamline your localization workflow! + +![image](https://user-images.githubusercontent.com/58659931/218817197-827d1934-4378-4ccb-87d3-a9118cb5203d.png) + +The application's main menu provides three options: find asynchronous keys, apply changes in the exported file, or replace keys. Let's take a look at each option in more details. + +![image](https://user-images.githubusercontent.com/58659931/218817488-0ba34e67-3039-4162-8291-5eb4669a8868.png) +![image](https://user-images.githubusercontent.com/58659931/218818263-d8d2d8c5-fc77-40a6-ba5c-e1fb7a0180be.png) +![image](https://user-images.githubusercontent.com/58659931/218818359-79a65d48-4895-4ed8-9d34-b6282939ca48.png) + +If you choose to find asynchronous keys, you will be prompted to enter the default language path. Once entered, a multi-select menu will appear with all the JSON files in that folder. You can choose to find keys that do not match the number of arguments, missing keys, or both. If you choose to find the missing keys, you will be prompted to enter the absolute path to export the missing keys. Once entered, the export process will begin, and the application will close. + +![image](https://user-images.githubusercontent.com/58659931/218818963-747766a5-b1c0-420f-95d2-7017a5f63925.png) + +If you choose to find the keys that do not match the number of arguments, you can choose to delete, export, or both. If you choose to delete, the process will complete, and the application will close. If you choose to export, you will be prompted to enter the export path. Once entered, the export process will begin, and the application will close. If you choose to do both, both processes will complete, and the application will close. + +![image](https://user-images.githubusercontent.com/58659931/218820012-e1596f88-5916-4f4f-a482-402c692ed207.png) + +If you choose to apply changes in the exported file, you will be prompted to import the file, and the process will complete. + +![image](https://user-images.githubusercontent.com/58659931/218820350-bfc0ccae-06e8-46d6-853d-7a2d1df3b156.png) + + If you choose to replace keys, you will be prompted to enter the localization folder path, the old key, the new key, and select the JSON files where you want the changes to be made. Once all information is entered, the application will begin the replacement process, and it will close. + +Overall, our console application offers a simple and effective solution to manage your localization files. Try it out yourself today! diff --git a/tools/localization-key-synchronizer/src/AbpAsyncKey.cs b/tools/localization-key-synchronizer/src/AbpAsyncKey.cs new file mode 100644 index 0000000000..e46bb2d7be --- /dev/null +++ b/tools/localization-key-synchronizer/src/AbpAsyncKey.cs @@ -0,0 +1,16 @@ +namespace LocalizationKeySynchronizer; + +public class AbpAsyncKey +{ + public string NewValue = string.Empty; + + public AbpAsyncKey(string key, string reference) + { + Key = key; + Reference = reference; + } + + public virtual string Type => GetType().Name; + public string Key { get; set; } + public string Reference { get; set; } +} \ No newline at end of file diff --git a/tools/localization-key-synchronizer/src/AbpAsyncLocalization.cs b/tools/localization-key-synchronizer/src/AbpAsyncLocalization.cs new file mode 100644 index 0000000000..396975f5b7 --- /dev/null +++ b/tools/localization-key-synchronizer/src/AbpAsyncLocalization.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; + +namespace LocalizationKeySynchronizer; + +public class AbpAsyncLocalization +{ + public AbpAsyncLocalization(AbpLocalization localization, AbpLocalization reference, List asyncKeys) + { + Localization = localization; + Reference = reference; + AsyncKeys = asyncKeys; + } + + public AbpLocalization Localization { get; set; } + public AbpLocalization Reference { get; set; } + + public List AsyncKeys { get; set; } +} \ No newline at end of file diff --git a/tools/localization-key-synchronizer/src/AbpAsyncLocalizationViewModel.cs b/tools/localization-key-synchronizer/src/AbpAsyncLocalizationViewModel.cs new file mode 100644 index 0000000000..c2a17ae378 --- /dev/null +++ b/tools/localization-key-synchronizer/src/AbpAsyncLocalizationViewModel.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; + +namespace LocalizationKeySynchronizer; + +public class AbpAsyncLocalizationViewModel +{ + public AbpAsyncLocalizationViewModel(string referenceCulture, string culture, string path, List asyncKeys) + { + ReferenceCulture = referenceCulture; + Culture = culture; + Path = path; + AsyncKeys = asyncKeys; + } + + public string ReferenceCulture { get; set; } + public string Culture { get; set; } + public string Path { get; set; } + + public List AsyncKeys { get; set; } +} \ No newline at end of file diff --git a/tools/localization-key-synchronizer/src/AbpLocalization.cs b/tools/localization-key-synchronizer/src/AbpLocalization.cs new file mode 100644 index 0000000000..78d82d556a --- /dev/null +++ b/tools/localization-key-synchronizer/src/AbpLocalization.cs @@ -0,0 +1,14 @@ +namespace LocalizationKeySynchronizer; + +public class AbpLocalization +{ + public AbpLocalization(string filePath, AbpLocalizationInfo localizationInfo) + { + FilePath = filePath; + LocalizationInfo = localizationInfo; + } + + public string FilePath { get; set; } + + public AbpLocalizationInfo LocalizationInfo { get; set; } +} \ No newline at end of file diff --git a/tools/localization-key-synchronizer/src/AbpLocalizationInfo.cs b/tools/localization-key-synchronizer/src/AbpLocalizationInfo.cs new file mode 100644 index 0000000000..d8db3b4b1b --- /dev/null +++ b/tools/localization-key-synchronizer/src/AbpLocalizationInfo.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; + +namespace LocalizationKeySynchronizer; + +// This class is used to deserialize the JSON string from culture file. +public class AbpLocalizationInfo +{ + public AbpLocalizationInfo(string culture, Dictionary texts) + { + Culture = culture; + Texts = texts; + } + + public string Culture { get; set; } + public Dictionary Texts { get; set; } + + public static bool TryDeserialize(string json, out AbpLocalizationInfo? localizationInfo) + { + return JsonHelper.TryDeserialize(json, out localizationInfo); + } +} \ No newline at end of file diff --git a/tools/localization-key-synchronizer/src/ArgumentCountMismatch.cs b/tools/localization-key-synchronizer/src/ArgumentCountMismatch.cs new file mode 100644 index 0000000000..1b3630f116 --- /dev/null +++ b/tools/localization-key-synchronizer/src/ArgumentCountMismatch.cs @@ -0,0 +1,15 @@ +namespace LocalizationKeySynchronizer; + +public class ArgumentCountMismatch : AbpAsyncKey +{ + public ArgumentCountMismatch(string key, string reference, int referenceArgumentCount, int argumentCount, string value) : base(key, reference) + { + ReferenceArgumentCount = referenceArgumentCount; + ArgumentCount = argumentCount; + Value = value; + } + + public int ReferenceArgumentCount { get; } + public int ArgumentCount { get; } + public string Value { get; } +} \ No newline at end of file diff --git a/tools/localization-key-synchronizer/src/JsonHelper.cs b/tools/localization-key-synchronizer/src/JsonHelper.cs new file mode 100644 index 0000000000..2f284d5839 --- /dev/null +++ b/tools/localization-key-synchronizer/src/JsonHelper.cs @@ -0,0 +1,26 @@ +using System; +using Newtonsoft.Json; + +namespace LocalizationKeySynchronizer; + +public static class JsonHelper +{ + public static bool TryDeserialize(string json, out T? result) + { + try + { + result = JsonConvert.DeserializeObject(json); + return true; + } + catch (Exception) + { + result = default; + return false; + } + } + + public static string Serialize(T value) + { + return JsonConvert.SerializeObject(value, Formatting.Indented); + } +} \ No newline at end of file diff --git a/tools/localization-key-synchronizer/src/LocalizationHelper.cs b/tools/localization-key-synchronizer/src/LocalizationHelper.cs new file mode 100644 index 0000000000..fbdb8a1aef --- /dev/null +++ b/tools/localization-key-synchronizer/src/LocalizationHelper.cs @@ -0,0 +1,173 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +namespace LocalizationKeySynchronizer; + +public static partial class LocalizationHelper +{ + public static bool TryGetLocalization(string path, out AbpLocalizationInfo? localizationInfo) + { + if (File.Exists(path) == false) + { + localizationInfo = default; + return false; + } + + var json = File.ReadAllTextAsync(path).GetAwaiter().GetResult(); + return AbpLocalizationInfo.TryDeserialize(json, out localizationInfo); + } + + public static List GetLocalizations(IEnumerable paths) + { + var results = new List(); + foreach (var path in paths) + { + if (TryGetLocalization(path, out var localizationInfo)) + { + results.Add(new AbpLocalization(path, localizationInfo!)); + } + } + + return results; + } + + private static Dictionary GetKeysAndArgCount(this AbpLocalizationInfo localizationInfo) + { + return localizationInfo.Texts.ToDictionary(k => k.Key, v => GetArgCount(v.Value)); + } + + private static int GetArgCount(string value) + { + var matches = MyRegex().Matches(value); + return matches.Count; + } + + public static List GetAsynchronousLocalizations(this AbpLocalization defaultLocalization, + IEnumerable otherLocalizations) + { + var results = new List(); + var defaultCultureKeysAndArgCount = defaultLocalization.LocalizationInfo.GetKeysAndArgCount(); + foreach (var localization in otherLocalizations) + { + var keysAndArgCount = localization.LocalizationInfo.GetKeysAndArgCount(); + var asynchronousResource = + new AbpAsyncLocalization(localization, defaultLocalization, new List()); + foreach (var (key, defaultCultureArgCount) in defaultCultureKeysAndArgCount) + { + if (keysAndArgCount.TryGetValue(key, out var value)) + { + if (value != defaultCultureArgCount) + { + asynchronousResource.AsyncKeys.Add(new ArgumentCountMismatch(key, + defaultLocalization.LocalizationInfo.Texts[key], defaultCultureArgCount, value, + localization.LocalizationInfo.Texts[key])); + } + } + else + { + asynchronousResource.AsyncKeys.Add(new MissingKey(key, + defaultLocalization.LocalizationInfo.Texts[key])); + } + } + + if (asynchronousResource.AsyncKeys.Any()) + { + results.Add(asynchronousResource); + } + } + + return results; + } + + public static void DeleteKeysThatDoNotMatchTheNumberOfArguments( + IEnumerable asynchronousResources) + { + foreach (var resource in asynchronousResources) + { + foreach (var key in resource.AsyncKeys.Select(x => x.Key)) + { + resource.Localization.LocalizationInfo.Texts.Remove(key); + } + + File.WriteAllTextAsync(resource.Localization.FilePath, + JsonHelper.Serialize(resource.Localization.LocalizationInfo)).GetAwaiter().GetResult(); + } + } + + public static void ExportKeysThatDoNotMatchTheNumberOfArguments( + IEnumerable asynchronousResources, string? exportPath) + { + Export(asynchronousResources, exportPath); + } + + public static void Export(IEnumerable asynchronousResources, string? exportPath) + where T : AbpAsyncKey + { + var asyncLocalizationViewModels = asynchronousResources.Select(x => + new AbpAsyncLocalizationViewModel(x.Reference.LocalizationInfo.Culture, + x.Localization.LocalizationInfo.Culture, x.Localization.FilePath, + x.AsyncKeys.Where(k => k is T).ToList())).ToList(); + + if (exportPath != null) + { + File.WriteAllTextAsync(exportPath, + JsonHelper.Serialize(asyncLocalizationViewModels)) + .GetAwaiter().GetResult(); + } + } + + public static void ExportMissingKeys(IEnumerable asyncLocalizations, string? exportPath) + { + Export(asyncLocalizations, exportPath); + } + + public static bool ApplyChanges(string path) + { + var json = File.ReadAllTextAsync(path).GetAwaiter().GetResult(); + + if (JsonHelper.TryDeserialize(json, out List? asyncLocalizationViewModels) == + false) + { + return false; + } + + foreach (var asyncLocalizationViewModel in asyncLocalizationViewModels!) + { + if (TryGetLocalization(asyncLocalizationViewModel.Path, out var localizationInfo) == false) + { + return false; + } + + foreach (var asyncKey in asyncLocalizationViewModel.AsyncKeys.Where(asyncKey => + !string.IsNullOrWhiteSpace(asyncKey.NewValue))) + { + localizationInfo!.Texts[asyncKey.Key] = asyncKey.NewValue; + } + + File.WriteAllTextAsync(asyncLocalizationViewModel.Path, + JsonHelper.Serialize(localizationInfo)).GetAwaiter().GetResult(); + } + + return true; + } + + public static void ReplaceKey(string oldKey, string newKey, List localizations) + { + foreach (var localization in localizations) + { + if (!localization.LocalizationInfo.Texts.TryGetValue(oldKey, out var value)) + { + continue; + } + + localization.LocalizationInfo.Texts.Remove(oldKey); + localization.LocalizationInfo.Texts.Add(newKey, value); + File.WriteAllTextAsync(localization.FilePath, + JsonHelper.Serialize(localization.LocalizationInfo)).GetAwaiter().GetResult(); + } + } + + [GeneratedRegex("{(\\d+)}")] + private static partial Regex MyRegex(); +} \ No newline at end of file diff --git a/tools/localization-key-synchronizer/src/LocalizationKeySynchronizer.csproj b/tools/localization-key-synchronizer/src/LocalizationKeySynchronizer.csproj new file mode 100644 index 0000000000..8f566bc239 --- /dev/null +++ b/tools/localization-key-synchronizer/src/LocalizationKeySynchronizer.csproj @@ -0,0 +1,15 @@ + + + + Exe + net7.0 + enable + enable + + + + + + + + diff --git a/tools/localization-key-synchronizer/src/MissingKey.cs b/tools/localization-key-synchronizer/src/MissingKey.cs new file mode 100644 index 0000000000..0463f3dd16 --- /dev/null +++ b/tools/localization-key-synchronizer/src/MissingKey.cs @@ -0,0 +1,8 @@ +namespace LocalizationKeySynchronizer; + +public class MissingKey : AbpAsyncKey +{ + public MissingKey(string key, string reference) : base(key, reference) + { + } +} \ No newline at end of file diff --git a/tools/localization-key-synchronizer/src/Program.cs b/tools/localization-key-synchronizer/src/Program.cs new file mode 100644 index 0000000000..42c97f0754 --- /dev/null +++ b/tools/localization-key-synchronizer/src/Program.cs @@ -0,0 +1,173 @@ +using System; +using System.IO; +using System.Linq; +using LocalizationKeySynchronizer; +using Spectre.Console; +using static LocalizationKeySynchronizer.Questions; + +try +{ + // Do you want to find asynchronous keys, apply changes in the exported file or replace the keys? + var option = AnsiConsole.Prompt( + new SelectionPrompt() + .Title(_1.Question) + .AddChoices(_1.Options.Find, _1.Options.Apply, _1.Options.Replace)); + + switch (option) + { + case _1.Options.Apply: + { + // Enter the absolute path to the exported file: + var path = AnsiConsole.Ask(_2); + + if (!File.Exists(path)) + { + AnsiConsole.MarkupLine("[red]The file does not exist![/]"); + Exit(); + } + + if (LocalizationHelper.ApplyChanges(path)) + { + AnsiConsole.MarkupLine("[green]The changes have been applied successfully![/]"); + Exit(); + } + + AnsiConsole.MarkupLine("[red]An error occurred while applying changes![/]"); + Exit(); + break; + } + case _1.Options.Find: + { + // The default language path + var path = AnsiConsole.Ask(_3); + + if (!LocalizationHelper.TryGetLocalization(path, out var defaultLocalizationInfo)) + { + AnsiConsole.MarkupLine("[red]The default language path is invalid![/]"); + Exit(); + } + + var defaultCulture = new AbpLocalization(path, defaultLocalizationInfo!); + +// Get others cultures + var paths = Directory.GetFiles(Path.GetDirectoryName(path)!, "*.json", + SearchOption.TopDirectoryOnly); + + var otherCulturePaths = paths.Select(Path.GetFileNameWithoutExtension).Where(x=>!string.IsNullOrWhiteSpace(x)).Select(x=>x!).ToList(); + // select other cultures + paths = AnsiConsole.Prompt( + new MultiSelectionPrompt() + .Title("Select other cultures") + .PageSize(10).AddChoiceGroup("All", otherCulturePaths)) + .Select(x => Path.Combine(Path.GetDirectoryName(path)!, x + ".json")) + .ToArray(); + + var otherCultures = LocalizationHelper.GetLocalizations(paths); + var asyncLocalizations = defaultCulture.GetAsynchronousLocalizations(otherCultures); + +// Find keys that do not match the number of arguments, find missing keys, or both + + var options = AnsiConsole.Prompt( + new MultiSelectionPrompt() + .Title(_4.Question) + .PageSize(10) + .AddChoiceGroup("All", _4.Options.ArgumentsCount, _4.Options.MissingKeys)); + +// For arguments +// Find keys that do not match the number of arguments + + string? exportPath = null; + if (options.Contains(_4.Options.ArgumentsCount)) + { + // Should the keys that do not match the number of arguments be deleted, exported or both? + + var options2 = AnsiConsole.Prompt( + new MultiSelectionPrompt() + .Title(_5.Question) + .PageSize(10) + .AddChoiceGroup("All", _5.Options.Delete, _5.Options.Export)); + + // Delete the keys that do not match the number of arguments + if (options2.Contains(_5.Options.Delete)) + { + LocalizationHelper.DeleteKeysThatDoNotMatchTheNumberOfArguments(asyncLocalizations); + } + + // Ask for the export path and export it + if (options2.Contains(_5.Options.Export)) + { + if (options.Contains(_4.Options.MissingKeys)) + { + exportPath = AnsiConsole.Ask(_8); + LocalizationHelper.Export(asyncLocalizations, exportPath); + } + else + { + exportPath = AnsiConsole.Ask(_6); + LocalizationHelper.ExportKeysThatDoNotMatchTheNumberOfArguments(asyncLocalizations, exportPath); + } + } + } + +// For missing keys +// Export missing keys + if (options.Contains(_4.Options.MissingKeys)) + { + if (string.IsNullOrEmpty(exportPath)) + { + exportPath = AnsiConsole.Ask(_7); + LocalizationHelper.ExportMissingKeys(asyncLocalizations, exportPath); + } + } + + break; + } + case _1.Options.Replace: + { + // The localization folder path + var path = AnsiConsole.Ask(_9); + + // Old key + var oldKey = AnsiConsole.Ask(_10); + + // New key + var newKey = AnsiConsole.Ask(_11); + + // Localization paths + var paths = Directory.GetFiles(path, "*.json", + SearchOption.TopDirectoryOnly); + + // Select localizations + + var localizationPaths = paths.Select(Path.GetFileNameWithoutExtension).Where(x=>!string.IsNullOrWhiteSpace(x)).Select(x=>x!).ToList(); + paths = AnsiConsole.Prompt( + new MultiSelectionPrompt() + .Title("Select localizations") + .PageSize(10) + .AddChoiceGroup("All", localizationPaths)) + .Select(x => Path.Combine(path, x + ".json")) + .ToArray(); + + var cultures = LocalizationHelper.GetLocalizations(paths); + + // Replace keys + LocalizationHelper.ReplaceKey(oldKey, newKey, cultures); + + AnsiConsole.MarkupLine("[green]The keys have been replaced successfully![/]"); + break; + } + } +} +catch (Exception e) +{ + Console.WriteLine(e); + AnsiConsole.MarkupLine($"[red]{e.Message}[/]"); + Exit(); +} + +void Exit() +{ + AnsiConsole.MarkupLine("[red]Press any key to exit...[/]"); + Console.ReadKey(); + Environment.Exit(0); +} \ No newline at end of file diff --git a/tools/localization-key-synchronizer/src/Questions.cs b/tools/localization-key-synchronizer/src/Questions.cs new file mode 100644 index 0000000000..a284fbaf9b --- /dev/null +++ b/tools/localization-key-synchronizer/src/Questions.cs @@ -0,0 +1,57 @@ +namespace LocalizationKeySynchronizer; + +public static class Questions +{ + public const string _2 = "Enter the absolute path to the exported file:"; + + public const string _3 = "Enter the default language path:"; + + public const string _6 = "Enter the absolute path to export the keys that do not match the number of arguments:"; + + public const string _7 = "Enter the absolute path to export the missing keys:"; + + public const string _8 = "Enter the export path:"; + + public const string _9 = "Enter the localization folder path:"; + + public const string _10 = "Enter the old key:"; + + public const string _11 = "Enter the new key:"; + + public static class _1 + { + public const string Question = + "Do you want to find asynchronous keys, apply changes in the exported file or replace the keys?"; + + public static class Options + { + public const string Find = "Find asynchronous keys"; + public const string Apply = "Apply changes in the exported file"; + public const string Replace = "Replace keys"; + } + } + + public static class _4 + { + public const string Question = + "Find keys that do not match the number of arguments, find missing keys, or both?"; + + public static class Options + { + public const string ArgumentsCount = "Not matching arguments count"; + public const string MissingKeys = "Missing keys"; + } + } + + public static class _5 + { + public const string Question = + "Should the keys that do not match the number of arguments be deleted, exported or both?"; + + public static class Options + { + public const string Delete = "Delete"; + public const string Export = "Export"; + } + } +} \ No newline at end of file