From f509b46bd8eb6d53063f88f697c1a04ba81986e8 Mon Sep 17 00:00:00 2001 From: gaviny82 <38808970+gaviny82@users.noreply.github.com> Date: Sat, 18 Jan 2025 18:03:38 +0000 Subject: [PATCH 01/12] Rewrite localizer --- .../FluentLauncher.Infra.Localizer.csproj | 7 +- FluentLauncher.Infra.Localizer/Program.cs | 198 ++++++++++++++++ .../Properties/launchSettings.json | 0 Scripts/LocalizerScript.sln | 31 --- Scripts/LocalizerScript/Program.cs | 212 ------------------ 5 files changed, 200 insertions(+), 248 deletions(-) rename Scripts/LocalizerScript/LocalizerScript.csproj => FluentLauncher.Infra.Localizer/FluentLauncher.Infra.Localizer.csproj (52%) create mode 100644 FluentLauncher.Infra.Localizer/Program.cs rename {Scripts/LocalizerScript => FluentLauncher.Infra.Localizer}/Properties/launchSettings.json (100%) delete mode 100644 Scripts/LocalizerScript.sln delete mode 100644 Scripts/LocalizerScript/Program.cs diff --git a/Scripts/LocalizerScript/LocalizerScript.csproj b/FluentLauncher.Infra.Localizer/FluentLauncher.Infra.Localizer.csproj similarity index 52% rename from Scripts/LocalizerScript/LocalizerScript.csproj rename to FluentLauncher.Infra.Localizer/FluentLauncher.Infra.Localizer.csproj index 29d8a9e..fb36471 100644 --- a/Scripts/LocalizerScript/LocalizerScript.csproj +++ b/FluentLauncher.Infra.Localizer/FluentLauncher.Infra.Localizer.csproj @@ -1,12 +1,9 @@ - + Exe net8.0 - enable - disable - ..\bin - AnyCPU;x64 + enable diff --git a/FluentLauncher.Infra.Localizer/Program.cs b/FluentLauncher.Infra.Localizer/Program.cs new file mode 100644 index 0000000..d69b2ff --- /dev/null +++ b/FluentLauncher.Infra.Localizer/Program.cs @@ -0,0 +1,198 @@ +using Csv; +using System; +using System.Collections.Generic; +using System.Data; +using System.IO; +using System.Linq; +using System.Text; + +// Input arguments +DirectoryInfo srcFolder = new(args[0]); +DirectoryInfo outFolder = new(args[1]); + +List languages = [ + "en-US", + "zh-Hans", + "zh-Hant", + "ru-RU", + "uk-UA" +]; + +List warnings = new(); +List errors = new(); + +// Init string resource table (key=language code, value=translated string resources) +var strings = new Dictionary>(); +foreach (string lang in languages) +{ + strings[lang] = new(); +} + +// Enumerate and parse all CSV files +foreach (FileInfo file in srcFolder.EnumerateFiles("*.csv", SearchOption.AllDirectories)) +{ + string relativePath = Path.GetRelativePath(srcFolder.FullName, file.FullName); + foreach (var str in ParseCsv(file, relativePath)) + { + foreach (string lang in languages) + { + string resourceId = relativePath[0..^".csv".Length].Replace(Path.DirectorySeparatorChar, '_') + "_" + str.GetName(); + strings[lang][resourceId] = str.Translations[lang]; + } + } + +} + +// Print warnings (missing translations) +Console.ForegroundColor = ConsoleColor.Yellow; + +foreach (var item in warnings) + Console.WriteLine(item); + +// Print errors (invalid CSV files) +Console.ForegroundColor = ConsoleColor.Red; + +foreach (var item in errors) + Console.WriteLine(item); + +if (errors.Count > 0) + Environment.Exit(-1); + +Console.ForegroundColor = ConsoleColor.Green; + +// Generate .resw files +if (!Directory.Exists(outFolder.FullName)) + Directory.CreateDirectory(outFolder.FullName); + +foreach (string lang in languages) +{ + // Build .resw file + var reswBuilder = new StringBuilder(); + + reswBuilder.AppendLine(""" + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + """); + + foreach ((string key, string translatedString) in strings[lang]) + { + reswBuilder.AppendLine($""" + + {translatedString} + + """); + } + + reswBuilder.AppendLine(""" + + """); + + // Write to file + var outputFile = new FileInfo(Path.Combine(outFolder.FullName, $"Resources.lang-{lang}.resw")); + File.WriteAllText(outputFile.FullName, reswBuilder.ToString()); + Console.WriteLine($"[INFO] 已生成资源文件:{outputFile.FullName}"); +} + + +// Parse a CSV file +IEnumerable ParseCsv(FileInfo csvFile, string relativePath) +{ + using var csvFileStream = csvFile.OpenRead(); + IEnumerable lines = CsvReader.ReadFromStream(csvFileStream) + .Select(line => ParseLine(line, relativePath)) + .Where(x => x is not null)!; + return lines; +} + +// Parse a line in the CSV file +StringResource? ParseLine(ICsvLine line, string relativePath) +{ + // Error checking for CSV file + if (!line.HasColumn("Id") || string.IsNullOrWhiteSpace(line["Id"])) + { + errors.Add($"[ERROR]:at {relativePath}, Line {line.Index} : 资源Id 不能为空"); + return null; + } + + if (!line.HasColumn("Property")) + { + errors.Add($"[ERROR]:at {relativePath}, Line {line.Index} : 缺少Property列"); + return null; + } + + if (line["Id"].StartsWith('_') && !string.IsNullOrEmpty(line["Property"])) + { + errors.Add($"[ERROR]:at {relativePath}, Line {line.Index} : 资源Id 标记为后台代码,但 资源属性Id 又不为空"); + return null; + } + + // Parse translations + Dictionary translations = new(); + + foreach (string lang in languages) + { + if (!line.HasColumn(lang)) + { + errors.Add($"[ERROR]:at {relativePath}, Line {line.Index} : 缺少语言 {lang}"); + return null; + } + + if (line[lang] == "") + { + warnings.Add($"[WARN]:at {relativePath}, Line {line.Index} : 缺少 {lang} 的翻译"); + } + + translations[lang] = line[lang]; + } + + var resource = new StringResource + { + Uid = line["Id"], + Property = line["Property"], + Translations = translations + }; + + return resource; +} + + +/// +/// Represents a string resource with translations for different languages +/// +record class StringResource +{ + /// + /// x:Uid of the component (if used in XAML) or ID of the resource (if used in code behind) + /// + public required string Uid { get; init; } + + /// + /// Property name of the component (if used in XAML) + /// + public required string Property { get; init; } + + /// + /// Translations for different languages (key=language code, value=translated string) + /// + public required Dictionary Translations { get; init; } + + public string GetName() + { + if (Uid.StartsWith('_')) + return Uid; + + return $"{Uid}.{Property}"; + } +} \ No newline at end of file diff --git a/Scripts/LocalizerScript/Properties/launchSettings.json b/FluentLauncher.Infra.Localizer/Properties/launchSettings.json similarity index 100% rename from Scripts/LocalizerScript/Properties/launchSettings.json rename to FluentLauncher.Infra.Localizer/Properties/launchSettings.json diff --git a/Scripts/LocalizerScript.sln b/Scripts/LocalizerScript.sln deleted file mode 100644 index 8953835..0000000 --- a/Scripts/LocalizerScript.sln +++ /dev/null @@ -1,31 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.6.33723.286 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LocalizerScript", "LocalizerScript\LocalizerScript.csproj", "{CA124E49-92CC-42E8-AB9D-66F1C2C76F12}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {CA124E49-92CC-42E8-AB9D-66F1C2C76F12}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CA124E49-92CC-42E8-AB9D-66F1C2C76F12}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CA124E49-92CC-42E8-AB9D-66F1C2C76F12}.Debug|x64.ActiveCfg = Debug|x64 - {CA124E49-92CC-42E8-AB9D-66F1C2C76F12}.Debug|x64.Build.0 = Debug|x64 - {CA124E49-92CC-42E8-AB9D-66F1C2C76F12}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CA124E49-92CC-42E8-AB9D-66F1C2C76F12}.Release|Any CPU.Build.0 = Release|Any CPU - {CA124E49-92CC-42E8-AB9D-66F1C2C76F12}.Release|x64.ActiveCfg = Release|x64 - {CA124E49-92CC-42E8-AB9D-66F1C2C76F12}.Release|x64.Build.0 = Release|x64 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {F6113517-B49A-4F64-B491-14F5DB7E6611} - EndGlobalSection -EndGlobal diff --git a/Scripts/LocalizerScript/Program.cs b/Scripts/LocalizerScript/Program.cs deleted file mode 100644 index c8012c7..0000000 --- a/Scripts/LocalizerScript/Program.cs +++ /dev/null @@ -1,212 +0,0 @@ -using System.Text; - -namespace LocalizerScript; - -internal class Program -{ - private static readonly List Languages = new() - { - "en-US", - "zh-Hans", - "zh-Hant", - "ru-RU", - "uk-UA" - }; - - private static DirectoryInfo OutputsFolder; - private static DirectoryInfo SourcesFolder; - - private static readonly List Warns = new(); - private static readonly List Errors = new(); - - static void Main(string[] args) - { - SourcesFolder = new DirectoryInfo(args[0]); - OutputsFolder = new DirectoryInfo(args[1]); - - var keyValuePairs = GetResources(SourcesFolder); - - var languages = new Dictionary>(); - int found = 0; - - foreach (var item in Languages) - languages.Add(item, new()); - - foreach (var keyValuePair in keyValuePairs) - { - var relativePath = keyValuePair.Key.Trim('\\'); - - foreach (var @string in keyValuePair.Value) - { - var Path_Name = relativePath.Replace(".csv", string.Empty).Replace('\\', '_') + "_" + @string.GetName(); - found++; - - int empty = 0; - - foreach (var value in @string.Values) - { - if (string.IsNullOrEmpty(value.Value)) - empty++; - - languages[value.Key].Add(Path_Name, value.Value); - } - - if (empty > 0) - Warns.Add($"[WARN]:at {relativePath}, 资源 {@string.GetName()} 有 {empty} 个空项"); - } - } - - Console.WriteLine($"[INFO] 已找到 {found} 个资源"); - - Console.ForegroundColor = ConsoleColor.Yellow; - - foreach (var item in Warns) - Console.WriteLine(item); - - Console.ForegroundColor = ConsoleColor.Red; - - foreach (var item in Errors) - Console.WriteLine(item); - - if (Errors.Count > 0) - throw new Exception("Invalid Csvs"); - - Console.ForegroundColor = ConsoleColor.Green; - - foreach (var item in languages) - { - var outputFile = new FileInfo(Path.Combine(OutputsFolder.FullName, "Strings", item.Key, "Resources.resw")); - - if (!outputFile.Directory.Exists) - outputFile.Directory.Create(); - - File.WriteAllText(outputFile.FullName, BuildResw(item.Value)); - Console.WriteLine($"[INFO] 已生成资源文件:{outputFile.FullName}"); - } - - Console.ReadLine(); - } - - private static Dictionary> GetResources(DirectoryInfo directory) - { - var dic = new Dictionary>(); - - foreach (var file in directory.EnumerateFiles()) - { - if (file.Extension.Equals(".csv")) - { - var csvLines = Csv.CsvReader.ReadFromText(File.ReadAllText(file.FullName)); - - var relativePath = file.FullName.Replace(SourcesFolder.FullName, string.Empty); - var lines = csvLines.Select(x => ParseLine(relativePath, x)).Where(x => x != null).ToList(); - - dic.Add(relativePath, lines); - } - } - - foreach (var directoryInfo in directory.EnumerateDirectories()) - foreach (var item in GetResources(directoryInfo)) - dic.Add(item.Key, item.Value); - - return dic; - } - - private static StringResource ParseLine(string relativePath, Csv.ICsvLine line) - { - var values = line.Values.ToList(); - - if (values.Count != Languages.Count + 2) - { - Errors.Add($"[ERROR]:at {relativePath}, Line {line.Index} : 项数目不正确"); - return null; - } - - if (string.IsNullOrEmpty(values[0])) - { - Errors.Add($"[ERROR]:at {relativePath}, Line {line.Index} : 资源Id 不能为空"); - return null; - } - - if (values[0].StartsWith('_') && !string.IsNullOrEmpty(values[1])) - { - Errors.Add($"[ERROR]:at {relativePath}, Line {line.Index} : 资源Id 标记为后台代码,但 资源属性Id 又不为空"); - return null; - } - - var @string = new StringResource - { - Uid = values[0], - Property = values[1] - }; - - values.RemoveAt(0); - values.RemoveAt(0); - - var dic = new Dictionary(); - - for (int i = 0; i < values.Count; i++) - dic.Add(Languages[i], values[i]); - - @string.Values = dic; - - return @string; - } - - static string head = """ - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - """; - - static string dataTemplate = """ - - ${Value} - - """; - - static string end = """ - - """; - - private static string BuildResw(Dictionary values) - { - var @string = new StringBuilder(); - - @string.AppendLine(head); - - foreach (var item in values) - @string.AppendLine(dataTemplate.Replace("${Name}", item.Key).Replace("${Value}", item.Value)); - - @string.AppendLine(end); - - return @string.ToString(); - } -} - -public class StringResource -{ - public string Uid { get; set; } - - public string Property { get; set; } - - public Dictionary Values { get; set; } - - public string GetName() - { - if (Uid.StartsWith('_')) - return Uid; - - return $"{Uid}.{Property}"; - } -} \ No newline at end of file From 78d45de9aff5c9835b27a4fee8f3d0aa3b87c403 Mon Sep 17 00:00:00 2001 From: gaviny82 <38808970+gaviny82@users.noreply.github.com> Date: Sat, 18 Jan 2025 18:52:34 +0000 Subject: [PATCH 02/12] Add language options --- .../FluentLauncher.Infra.Localizer.csproj | 1 + FluentLauncher.Infra.Localizer/Program.cs | 150 ++++++++++-------- 2 files changed, 86 insertions(+), 65 deletions(-) diff --git a/FluentLauncher.Infra.Localizer/FluentLauncher.Infra.Localizer.csproj b/FluentLauncher.Infra.Localizer/FluentLauncher.Infra.Localizer.csproj index fb36471..b81bf97 100644 --- a/FluentLauncher.Infra.Localizer/FluentLauncher.Infra.Localizer.csproj +++ b/FluentLauncher.Infra.Localizer/FluentLauncher.Infra.Localizer.csproj @@ -8,6 +8,7 @@ + diff --git a/FluentLauncher.Infra.Localizer/Program.cs b/FluentLauncher.Infra.Localizer/Program.cs index d69b2ff..87f49dc 100644 --- a/FluentLauncher.Infra.Localizer/Program.cs +++ b/FluentLauncher.Infra.Localizer/Program.cs @@ -1,75 +1,90 @@ using Csv; using System; using System.Collections.Generic; +using System.CommandLine; +using System.CommandLine.Parsing; using System.Data; using System.IO; using System.Linq; using System.Text; -// Input arguments -DirectoryInfo srcFolder = new(args[0]); -DirectoryInfo outFolder = new(args[1]); +List Warnings = new(); +List Errors = new(); -List languages = [ - "en-US", - "zh-Hans", - "zh-Hant", - "ru-RU", - "uk-UA" -]; - -List warnings = new(); -List errors = new(); - -// Init string resource table (key=language code, value=translated string resources) -var strings = new Dictionary>(); -foreach (string lang in languages) +var srcOption = new Option("--src", "The source folder containing the CSV files") { IsRequired = true }; +var outOption = new Option("--out", "The output folder for .resw files") { IsRequired = true }; +var languagesOption = new Option>("--languages", "All languages for translation") { IsRequired = true, AllowMultipleArgumentsPerToken = true }; +var defaultLanguageOption = new Option("--default-language", () => "", "Default language of the app"); +defaultLanguageOption.AddValidator(result => { - strings[lang] = new(); -} - -// Enumerate and parse all CSV files -foreach (FileInfo file in srcFolder.EnumerateFiles("*.csv", SearchOption.AllDirectories)) + IEnumerable languages = result.GetValueForOption(languagesOption)!; + string defaultLanguage = result.GetValueForOption(defaultLanguageOption)!; + if (!languages.Contains(defaultLanguage)) + result.ErrorMessage = "Default language must be in the list of languages"; +}); + +var rootCommand = new RootCommand("Convert CSV files to .resw files for UWP/WinUI localization"); +rootCommand.AddOption(srcOption); +rootCommand.AddOption(outOption); +rootCommand.AddOption(languagesOption); +rootCommand.AddOption(defaultLanguageOption); +rootCommand.SetHandler(ConvertCsvToResw, srcOption, outOption, languagesOption, defaultLanguageOption); +rootCommand.Invoke(args); + +void ConvertCsvToResw(string srcPath, string outPath, IEnumerable languages, string defaultLanguage) { - string relativePath = Path.GetRelativePath(srcFolder.FullName, file.FullName); - foreach (var str in ParseCsv(file, relativePath)) + DirectoryInfo srcFolder = new(srcPath); + DirectoryInfo outFolder = new(outPath); + + // Init string resource table (key=language code, value=translated string resources) + var strings = new Dictionary>(); + foreach (string lang in languages) { - foreach (string lang in languages) + strings[lang] = new(); + } + + // Enumerate and parse all CSV files + foreach (FileInfo file in srcFolder.EnumerateFiles("*.csv", SearchOption.AllDirectories)) + { + string relativePath = Path.GetRelativePath(srcFolder.FullName, file.FullName); + foreach (var str in ParseCsv(file, relativePath, languages)) { - string resourceId = relativePath[0..^".csv".Length].Replace(Path.DirectorySeparatorChar, '_') + "_" + str.GetName(); - strings[lang][resourceId] = str.Translations[lang]; + foreach (string lang in languages) + { + string resourceId = relativePath[0..^".csv".Length].Replace(Path.DirectorySeparatorChar, '_') + "_" + str.GetName(); + strings[lang][resourceId] = str.Translations[lang]; + } } - } -} + } -// Print warnings (missing translations) -Console.ForegroundColor = ConsoleColor.Yellow; + // Print warnings (missing translations) + Console.ForegroundColor = ConsoleColor.Yellow; -foreach (var item in warnings) - Console.WriteLine(item); + foreach (var item in Warnings) + Console.WriteLine(item); -// Print errors (invalid CSV files) -Console.ForegroundColor = ConsoleColor.Red; + // Print errors (invalid CSV files) + Console.ForegroundColor = ConsoleColor.Red; -foreach (var item in errors) - Console.WriteLine(item); + foreach (var item in Errors) + Console.WriteLine(item); -if (errors.Count > 0) - Environment.Exit(-1); + if (Errors.Count > 0) + Environment.Exit(-1); -Console.ForegroundColor = ConsoleColor.Green; + Console.ForegroundColor = ConsoleColor.Green; -// Generate .resw files -if (!Directory.Exists(outFolder.FullName)) - Directory.CreateDirectory(outFolder.FullName); + // Generate .resw files + if (!Directory.Exists(outFolder.FullName)) + Directory.CreateDirectory(outFolder.FullName); -foreach (string lang in languages) -{ - // Build .resw file - var reswBuilder = new StringBuilder(); + foreach (string lang in languages) + { + // Build .resw file + var reswBuilder = new StringBuilder(); - reswBuilder.AppendLine(""" + reswBuilder.AppendLine(""" @@ -86,55 +101,60 @@ """); - foreach ((string key, string translatedString) in strings[lang]) - { - reswBuilder.AppendLine($""" + foreach ((string key, string translatedString) in strings[lang]) + { + reswBuilder.AppendLine($""" {translatedString} """); - } + } - reswBuilder.AppendLine(""" + reswBuilder.AppendLine(""" """); - // Write to file - var outputFile = new FileInfo(Path.Combine(outFolder.FullName, $"Resources.lang-{lang}.resw")); - File.WriteAllText(outputFile.FullName, reswBuilder.ToString()); - Console.WriteLine($"[INFO] 已生成资源文件:{outputFile.FullName}"); + // Write to file + + string outputPath = lang == defaultLanguage + ? Path.Combine(outFolder.FullName, $"Resources.resw") + : Path.Combine(outFolder.FullName, $"Resources.lang-{lang}.resw"); + var outputFile = new FileInfo(outputPath); + File.WriteAllText(outputFile.FullName, reswBuilder.ToString()); + Console.WriteLine($"[INFO] 已生成资源文件:{outputFile.FullName}"); + } } // Parse a CSV file -IEnumerable ParseCsv(FileInfo csvFile, string relativePath) +IEnumerable ParseCsv(FileInfo csvFile, string relativePath, IEnumerable languages) { using var csvFileStream = csvFile.OpenRead(); IEnumerable lines = CsvReader.ReadFromStream(csvFileStream) - .Select(line => ParseLine(line, relativePath)) + .Select(line => ParseLine(line, relativePath, languages)) .Where(x => x is not null)!; return lines; } // Parse a line in the CSV file -StringResource? ParseLine(ICsvLine line, string relativePath) +StringResource? ParseLine(ICsvLine line, string relativePath, IEnumerable languages) { // Error checking for CSV file if (!line.HasColumn("Id") || string.IsNullOrWhiteSpace(line["Id"])) { - errors.Add($"[ERROR]:at {relativePath}, Line {line.Index} : 资源Id 不能为空"); + Errors.Add($"[ERROR]:at {relativePath}, Line {line.Index} : 资源Id 不能为空"); return null; } if (!line.HasColumn("Property")) { - errors.Add($"[ERROR]:at {relativePath}, Line {line.Index} : 缺少Property列"); + Errors.Add($"[ERROR]:at {relativePath}, Line {line.Index} : 缺少Property列"); return null; } if (line["Id"].StartsWith('_') && !string.IsNullOrEmpty(line["Property"])) { - errors.Add($"[ERROR]:at {relativePath}, Line {line.Index} : 资源Id 标记为后台代码,但 资源属性Id 又不为空"); + Errors.Add($"[ERROR]:at {relativePath}, Line {line.Index} : 资源Id 标记为后台代码,但 资源属性Id 又不为空"); return null; } @@ -145,13 +165,13 @@ IEnumerable ParseCsv(FileInfo csvFile, string relativePath) { if (!line.HasColumn(lang)) { - errors.Add($"[ERROR]:at {relativePath}, Line {line.Index} : 缺少语言 {lang}"); + Errors.Add($"[ERROR]:at {relativePath}, Line {line.Index} : 缺少语言 {lang}"); return null; } if (line[lang] == "") { - warnings.Add($"[WARN]:at {relativePath}, Line {line.Index} : 缺少 {lang} 的翻译"); + Warnings.Add($"[WARN]:at {relativePath}, Line {line.Index} : 缺少 {lang} 的翻译"); } translations[lang] = line[lang]; From bcd4ddeec7a261e5325c3bfc49cb7f4c2a81873c Mon Sep 17 00:00:00 2001 From: gaviny82 <38808970+gaviny82@users.noreply.github.com> Date: Sat, 18 Jan 2025 18:53:34 +0000 Subject: [PATCH 03/12] Update command description --- FluentLauncher.Infra.Localizer/Program.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FluentLauncher.Infra.Localizer/Program.cs b/FluentLauncher.Infra.Localizer/Program.cs index 87f49dc..1a25c25 100644 --- a/FluentLauncher.Infra.Localizer/Program.cs +++ b/FluentLauncher.Infra.Localizer/Program.cs @@ -11,7 +11,7 @@ List Warnings = new(); List Errors = new(); -var srcOption = new Option("--src", "The source folder containing the CSV files") { IsRequired = true }; +var srcOption = new Option("--src", "The source folder containing the .csv files") { IsRequired = true }; var outOption = new Option("--out", "The output folder for .resw files") { IsRequired = true }; var languagesOption = new Option>("--languages", "All languages for translation") { IsRequired = true, AllowMultipleArgumentsPerToken = true }; var defaultLanguageOption = new Option("--default-language", () => "", "Default language of the app"); @@ -23,7 +23,7 @@ result.ErrorMessage = "Default language must be in the list of languages"; }); -var rootCommand = new RootCommand("Convert CSV files to .resw files for UWP/WinUI localization"); +var rootCommand = new RootCommand("Convert .csv files to .resw files for UWP/WinUI localization"); rootCommand.AddOption(srcOption); rootCommand.AddOption(outOption); rootCommand.AddOption(languagesOption); From f4b0524e94437444daa590b0e7d4760a1cd2872a Mon Sep 17 00:00:00 2001 From: gaviny82 <38808970+gaviny82@users.noreply.github.com> Date: Sat, 18 Jan 2025 19:36:09 +0000 Subject: [PATCH 04/12] Fix errors --- FluentLauncher.Infra.Localizer/Program.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/FluentLauncher.Infra.Localizer/Program.cs b/FluentLauncher.Infra.Localizer/Program.cs index 1a25c25..65547ca 100644 --- a/FluentLauncher.Infra.Localizer/Program.cs +++ b/FluentLauncher.Infra.Localizer/Program.cs @@ -19,7 +19,7 @@ { IEnumerable languages = result.GetValueForOption(languagesOption)!; string defaultLanguage = result.GetValueForOption(defaultLanguageOption)!; - if (!languages.Contains(defaultLanguage)) + if (defaultLanguage != "" && !languages.Contains(defaultLanguage)) result.ErrorMessage = "Default language must be in the list of languages"; }); @@ -129,8 +129,7 @@ void ConvertCsvToResw(string srcPath, string outPath, IEnumerable langua // Parse a CSV file IEnumerable ParseCsv(FileInfo csvFile, string relativePath, IEnumerable languages) { - using var csvFileStream = csvFile.OpenRead(); - IEnumerable lines = CsvReader.ReadFromStream(csvFileStream) + IEnumerable lines = CsvReader.ReadFromText(File.ReadAllText(csvFile.FullName)) .Select(line => ParseLine(line, relativePath, languages)) .Where(x => x is not null)!; return lines; From 43d369b1a78f1f8e44697fd62904775338cea094 Mon Sep 17 00:00:00 2001 From: gaviny82 <38808970+gaviny82@users.noreply.github.com> Date: Sat, 18 Jan 2025 19:36:13 +0000 Subject: [PATCH 05/12] Add debug profile --- .../Properties/launchSettings.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/FluentLauncher.Infra.Localizer/Properties/launchSettings.json b/FluentLauncher.Infra.Localizer/Properties/launchSettings.json index e1448b9..1ce140d 100644 --- a/FluentLauncher.Infra.Localizer/Properties/launchSettings.json +++ b/FluentLauncher.Infra.Localizer/Properties/launchSettings.json @@ -1,9 +1,9 @@ { "profiles": { - "LocalizerScript": { + "GenerateReswFiles": { "commandName": "Project", - "commandLineArgs": ".\\Views .\\", - "workingDirectory": "..\\..\\" + "commandLineArgs": "--src Views --out Strings --languages en-US zh-Hans zh-Hant ru-RU uk-UA", + "workingDirectory": ".." } } } \ No newline at end of file From 9c52160a29c5c443766bed3d479dbbb3e950e849 Mon Sep 17 00:00:00 2001 From: gaviny82 <38808970+gaviny82@users.noreply.github.com> Date: Sat, 18 Jan 2025 20:28:51 +0000 Subject: [PATCH 06/12] Update log --- FluentLauncher.Infra.Localizer/Program.cs | 75 +++++++++++++++-------- 1 file changed, 49 insertions(+), 26 deletions(-) diff --git a/FluentLauncher.Infra.Localizer/Program.cs b/FluentLauncher.Infra.Localizer/Program.cs index 65547ca..0675216 100644 --- a/FluentLauncher.Infra.Localizer/Program.cs +++ b/FluentLauncher.Infra.Localizer/Program.cs @@ -58,12 +58,6 @@ void ConvertCsvToResw(string srcPath, string outPath, IEnumerable langua } - // Print warnings (missing translations) - Console.ForegroundColor = ConsoleColor.Yellow; - - foreach (var item in Warnings) - Console.WriteLine(item); - // Print errors (invalid CSV files) Console.ForegroundColor = ConsoleColor.Red; @@ -71,7 +65,16 @@ void ConvertCsvToResw(string srcPath, string outPath, IEnumerable langua Console.WriteLine(item); if (Errors.Count > 0) + { + Console.WriteLine($"Failed to generate .resw files due to {Errors.Count} errors."); Environment.Exit(-1); + } + + // Print warnings (missing translations) + Console.ForegroundColor = ConsoleColor.Yellow; + + foreach (var item in Warnings) + Console.WriteLine(item); Console.ForegroundColor = ConsoleColor.Green; @@ -121,7 +124,7 @@ void ConvertCsvToResw(string srcPath, string outPath, IEnumerable langua : Path.Combine(outFolder.FullName, $"Resources.lang-{lang}.resw"); var outputFile = new FileInfo(outputPath); File.WriteAllText(outputFile.FullName, reswBuilder.ToString()); - Console.WriteLine($"[INFO] 已生成资源文件:{outputFile.FullName}"); + Console.WriteLine($"[INFO] Generated translation for {lang}: {outputFile.FullName}"); } } @@ -129,7 +132,39 @@ void ConvertCsvToResw(string srcPath, string outPath, IEnumerable langua // Parse a CSV file IEnumerable ParseCsv(FileInfo csvFile, string relativePath, IEnumerable languages) { - IEnumerable lines = CsvReader.ReadFromText(File.ReadAllText(csvFile.FullName)) + var csvLines = CsvReader.ReadFromText(File.ReadAllText(csvFile.FullName)); + + // Check CSV headers + var line = csvLines.FirstOrDefault(); + if (line is null) // Empty file + return []; + + bool invalid = false; + if (!line.HasColumn("Id")) + { + Errors.Add($"[ERROR] {relativePath}: Missing column \"Id\""); + invalid = true; + } + + if (!line.HasColumn("Property")) + { + Errors.Add($"[ERROR] {relativePath}: Missing column \"Property\""); + invalid = true; + } + + foreach (string lang in languages) + { + if (!line.HasColumn(lang)) + { + Errors.Add($"[ERROR] {relativePath}: Missing column for translation to {lang}"); + invalid = true; + } + } + + if (invalid) return []; + + // Parse lines + IEnumerable lines = csvLines .Select(line => ParseLine(line, relativePath, languages)) .Where(x => x is not null)!; return lines; @@ -138,22 +173,16 @@ IEnumerable ParseCsv(FileInfo csvFile, string relativePath, IEnu // Parse a line in the CSV file StringResource? ParseLine(ICsvLine line, string relativePath, IEnumerable languages) { - // Error checking for CSV file - if (!line.HasColumn("Id") || string.IsNullOrWhiteSpace(line["Id"])) - { - Errors.Add($"[ERROR]:at {relativePath}, Line {line.Index} : 资源Id 不能为空"); - return null; - } - - if (!line.HasColumn("Property")) + // Error checking + if (string.IsNullOrWhiteSpace(line["Id"])) { - Errors.Add($"[ERROR]:at {relativePath}, Line {line.Index} : 缺少Property列"); + Errors.Add($"[ERROR] {relativePath}, Line {line.Index}: Id must not be empty"); return null; } if (line["Id"].StartsWith('_') && !string.IsNullOrEmpty(line["Property"])) { - Errors.Add($"[ERROR]:at {relativePath}, Line {line.Index} : 资源Id 标记为后台代码,但 资源属性Id 又不为空"); + Errors.Add($"[ERROR] {relativePath}, Line {line.Index}: Property must be empty for strings for code-behind"); return null; } @@ -162,15 +191,9 @@ IEnumerable ParseCsv(FileInfo csvFile, string relativePath, IEnu foreach (string lang in languages) { - if (!line.HasColumn(lang)) - { - Errors.Add($"[ERROR]:at {relativePath}, Line {line.Index} : 缺少语言 {lang}"); - return null; - } - - if (line[lang] == "") + if (line[lang] == "") // Missing translation { - Warnings.Add($"[WARN]:at {relativePath}, Line {line.Index} : 缺少 {lang} 的翻译"); + Warnings.Add($"[WARNING] {relativePath}, Line {line.Index}: Missing translation to {lang}"); } translations[lang] = line[lang]; From 87c26eaaa7a2531216a9bb88f09c4f7a443f4b43 Mon Sep 17 00:00:00 2001 From: gaviny82 <38808970+gaviny82@users.noreply.github.com> Date: Sat, 18 Jan 2025 20:29:23 +0000 Subject: [PATCH 07/12] Remove build.bat --- build.bat | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 build.bat diff --git a/build.bat b/build.bat deleted file mode 100644 index f6ee1b9..0000000 --- a/build.bat +++ /dev/null @@ -1,6 +0,0 @@ -@echo off -cd .\Scripts -dotnet build --configuration Release /p:Platform="Any CPU" -cd ..\ -.\Scripts\bin\Release\net8.0\LocalizerScript.exe .\Views .\ -pause \ No newline at end of file From 81953ea9d4c8f0b022b616e339bcc0348da51fb9 Mon Sep 17 00:00:00 2001 From: gaviny82 <38808970+gaviny82@users.noreply.github.com> Date: Sat, 18 Jan 2025 20:39:25 +0000 Subject: [PATCH 08/12] Fix translation --- Views/Dictionaries/TaskViewStyleDictionary.csv | 2 +- Views/ShellPage.csv | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Views/Dictionaries/TaskViewStyleDictionary.csv b/Views/Dictionaries/TaskViewStyleDictionary.csv index 87fdf0d..a4714ee 100644 --- a/Views/Dictionaries/TaskViewStyleDictionary.csv +++ b/Views/Dictionaries/TaskViewStyleDictionary.csv @@ -1,4 +1,4 @@ -Id,Property,en-US,zh-Hans,zh-Hant,ru-RU,uk-UA +Id,Property,en-US,zh-Hans,zh-Hant,ru-RU,uk-UA DownloadGameResourceTitle,Text,Download Resource:,下载资源:,下載資源:,Ресурс загрузки: ,Ресурс завантаження: InstallInstanceTitle,Text,Install Instance:,安装实例:,安裝實例:,Установить экземпляр: ,Встановити екземпляр: LaunchTitle,Text,Launch Minecraft:,启动 Minecraft:,啟動 Minecraft:,Запустить Minecraft: ,Запустити Minecraft: diff --git a/Views/ShellPage.csv b/Views/ShellPage.csv index 60ed662..7b85995 100644 --- a/Views/ShellPage.csv +++ b/Views/ShellPage.csv @@ -1,4 +1,4 @@ -Id,Property,en-US,zh-Hans,zh-Hant,ru-RU +Id,Property,en-US,zh-Hans,zh-Hant,ru-RU,uk-UA NV_Item_1,Content,Home,主页面,主頁面,Главная,Головна NV_Item_2,Content,Cores,核心,核心,Ядра,Ядра NV_Item_3,Content,Download Resources,资源下载,資源下載,Скачать Ресурсы,Завантажити Ресурси From 972075c673cac868c8a973f09bb882afdfc1be0f Mon Sep 17 00:00:00 2001 From: gaviny82 <38808970+gaviny82@users.noreply.github.com> Date: Sat, 18 Jan 2025 20:39:34 +0000 Subject: [PATCH 09/12] Update log --- FluentLauncher.Infra.Localizer/Program.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/FluentLauncher.Infra.Localizer/Program.cs b/FluentLauncher.Infra.Localizer/Program.cs index 0675216..4f40433 100644 --- a/FluentLauncher.Infra.Localizer/Program.cs +++ b/FluentLauncher.Infra.Localizer/Program.cs @@ -126,6 +126,7 @@ void ConvertCsvToResw(string srcPath, string outPath, IEnumerable langua File.WriteAllText(outputFile.FullName, reswBuilder.ToString()); Console.WriteLine($"[INFO] Generated translation for {lang}: {outputFile.FullName}"); } + Console.WriteLine($"Successfully generated {strings.First().Value.Count} translations for {languages.Count()} languages."); } From 7b612bffe4d291830e6a09b17a062f3677d90f57 Mon Sep 17 00:00:00 2001 From: gaviny82 <38808970+gaviny82@users.noreply.github.com> Date: Sat, 18 Jan 2025 21:09:33 +0000 Subject: [PATCH 10/12] Fix CI --- .github/workflows/ci.yml | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7e5f811..706ceec 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,24 +21,8 @@ jobs: with: dotnet-version: 8.0.x - - name: Build LocalizerScript - run: | - cd .\Scripts - dotnet build - - - name: Run LocalizerScript - run: | - cd .\Scripts\bin\Debug\net8.0 - .\LocalizerScript.exe "${{github.workspace}}/Views" "${{github.workspace}}/Strings" - - - name: Upload .msixupload to artifacts - uses: actions/upload-artifact@v4 - with: - name: Strings - path: "${{github.workspace}}/Strings" - - - - - + - name: Build Localizer + run: dotnet build --project FluentLauncher.Infra.Localizer + - name: Run Localizer + run: dotnet run --project FluentLauncher.Infra.Localizer -- --src "Views" --out "Strings" --languages en-US zh-Hans zh-Hant ru-RU uk-UA From ecb26a0f323f6ececb37bcb523a1c2f612b0ac30 Mon Sep 17 00:00:00 2001 From: gaviny82 <38808970+gaviny82@users.noreply.github.com> Date: Sat, 18 Jan 2025 21:11:20 +0000 Subject: [PATCH 11/12] Update CI --- .github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 706ceec..95ecf92 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,8 +1,10 @@ name: CI on: - pull_request: push: + branches: ["main"] + pull_request: + branches: ["main"] workflow_dispatch: inputs: is_build: From b482d6b71b8b3e2285a5e63c86e5e4447573078a Mon Sep 17 00:00:00 2001 From: gaviny82 <38808970+gaviny82@users.noreply.github.com> Date: Sat, 18 Jan 2025 21:12:02 +0000 Subject: [PATCH 12/12] Fix CI --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 95ecf92..36cea23 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: dotnet-version: 8.0.x - name: Build Localizer - run: dotnet build --project FluentLauncher.Infra.Localizer + run: dotnet build FluentLauncher.Infra.Localizer - name: Run Localizer run: dotnet run --project FluentLauncher.Infra.Localizer -- --src "Views" --out "Strings" --languages en-US zh-Hans zh-Hant ru-RU uk-UA