Skip to content

Commit 78d45de

Browse files
committed
Add language options
1 parent f509b46 commit 78d45de

File tree

2 files changed

+86
-65
lines changed

2 files changed

+86
-65
lines changed

FluentLauncher.Infra.Localizer/FluentLauncher.Infra.Localizer.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
<ItemGroup>
1010
<PackageReference Include="Csv" Version="2.0.93" />
11+
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
1112
</ItemGroup>
1213

1314
</Project>

FluentLauncher.Infra.Localizer/Program.cs

Lines changed: 85 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,90 @@
11
using Csv;
22
using System;
33
using System.Collections.Generic;
4+
using System.CommandLine;
5+
using System.CommandLine.Parsing;
46
using System.Data;
57
using System.IO;
68
using System.Linq;
79
using System.Text;
810

9-
// Input arguments
10-
DirectoryInfo srcFolder = new(args[0]);
11-
DirectoryInfo outFolder = new(args[1]);
11+
List<string> Warnings = new();
12+
List<string> Errors = new();
1213

13-
List<string> languages = [
14-
"en-US",
15-
"zh-Hans",
16-
"zh-Hant",
17-
"ru-RU",
18-
"uk-UA"
19-
];
20-
21-
List<string> warnings = new();
22-
List<string> errors = new();
23-
24-
// Init string resource table (key=language code, value=translated string resources)
25-
var strings = new Dictionary<string, Dictionary<string, string>>();
26-
foreach (string lang in languages)
14+
var srcOption = new Option<string>("--src", "The source folder containing the CSV files") { IsRequired = true };
15+
var outOption = new Option<string>("--out", "The output folder for .resw files") { IsRequired = true };
16+
var languagesOption = new Option<IEnumerable<string>>("--languages", "All languages for translation") { IsRequired = true, AllowMultipleArgumentsPerToken = true };
17+
var defaultLanguageOption = new Option<string>("--default-language", () => "", "Default language of the app");
18+
defaultLanguageOption.AddValidator(result =>
2719
{
28-
strings[lang] = new();
29-
}
30-
31-
// Enumerate and parse all CSV files
32-
foreach (FileInfo file in srcFolder.EnumerateFiles("*.csv", SearchOption.AllDirectories))
20+
IEnumerable<string> languages = result.GetValueForOption(languagesOption)!;
21+
string defaultLanguage = result.GetValueForOption(defaultLanguageOption)!;
22+
if (!languages.Contains(defaultLanguage))
23+
result.ErrorMessage = "Default language must be in the list of languages";
24+
});
25+
26+
var rootCommand = new RootCommand("Convert CSV files to .resw files for UWP/WinUI localization");
27+
rootCommand.AddOption(srcOption);
28+
rootCommand.AddOption(outOption);
29+
rootCommand.AddOption(languagesOption);
30+
rootCommand.AddOption(defaultLanguageOption);
31+
rootCommand.SetHandler(ConvertCsvToResw, srcOption, outOption, languagesOption, defaultLanguageOption);
32+
rootCommand.Invoke(args);
33+
34+
void ConvertCsvToResw(string srcPath, string outPath, IEnumerable<string> languages, string defaultLanguage)
3335
{
34-
string relativePath = Path.GetRelativePath(srcFolder.FullName, file.FullName);
35-
foreach (var str in ParseCsv(file, relativePath))
36+
DirectoryInfo srcFolder = new(srcPath);
37+
DirectoryInfo outFolder = new(outPath);
38+
39+
// Init string resource table (key=language code, value=translated string resources)
40+
var strings = new Dictionary<string, Dictionary<string, string>>();
41+
foreach (string lang in languages)
3642
{
37-
foreach (string lang in languages)
43+
strings[lang] = new();
44+
}
45+
46+
// Enumerate and parse all CSV files
47+
foreach (FileInfo file in srcFolder.EnumerateFiles("*.csv", SearchOption.AllDirectories))
48+
{
49+
string relativePath = Path.GetRelativePath(srcFolder.FullName, file.FullName);
50+
foreach (var str in ParseCsv(file, relativePath, languages))
3851
{
39-
string resourceId = relativePath[0..^".csv".Length].Replace(Path.DirectorySeparatorChar, '_') + "_" + str.GetName();
40-
strings[lang][resourceId] = str.Translations[lang];
52+
foreach (string lang in languages)
53+
{
54+
string resourceId = relativePath[0..^".csv".Length].Replace(Path.DirectorySeparatorChar, '_') + "_" + str.GetName();
55+
strings[lang][resourceId] = str.Translations[lang];
56+
}
4157
}
42-
}
4358

44-
}
59+
}
4560

46-
// Print warnings (missing translations)
47-
Console.ForegroundColor = ConsoleColor.Yellow;
61+
// Print warnings (missing translations)
62+
Console.ForegroundColor = ConsoleColor.Yellow;
4863

49-
foreach (var item in warnings)
50-
Console.WriteLine(item);
64+
foreach (var item in Warnings)
65+
Console.WriteLine(item);
5166

52-
// Print errors (invalid CSV files)
53-
Console.ForegroundColor = ConsoleColor.Red;
67+
// Print errors (invalid CSV files)
68+
Console.ForegroundColor = ConsoleColor.Red;
5469

55-
foreach (var item in errors)
56-
Console.WriteLine(item);
70+
foreach (var item in Errors)
71+
Console.WriteLine(item);
5772

58-
if (errors.Count > 0)
59-
Environment.Exit(-1);
73+
if (Errors.Count > 0)
74+
Environment.Exit(-1);
6075

61-
Console.ForegroundColor = ConsoleColor.Green;
76+
Console.ForegroundColor = ConsoleColor.Green;
6277

63-
// Generate .resw files
64-
if (!Directory.Exists(outFolder.FullName))
65-
Directory.CreateDirectory(outFolder.FullName);
78+
// Generate .resw files
79+
if (!Directory.Exists(outFolder.FullName))
80+
Directory.CreateDirectory(outFolder.FullName);
6681

67-
foreach (string lang in languages)
68-
{
69-
// Build .resw file
70-
var reswBuilder = new StringBuilder();
82+
foreach (string lang in languages)
83+
{
84+
// Build .resw file
85+
var reswBuilder = new StringBuilder();
7186

72-
reswBuilder.AppendLine("""
87+
reswBuilder.AppendLine("""
7388
<?xml version="1.0" encoding="utf-8"?>
7489
<root>
7590
<resheader name="resmimetype">
@@ -86,55 +101,60 @@
86101
</resheader>
87102
""");
88103

89-
foreach ((string key, string translatedString) in strings[lang])
90-
{
91-
reswBuilder.AppendLine($"""
104+
foreach ((string key, string translatedString) in strings[lang])
105+
{
106+
reswBuilder.AppendLine($"""
92107
<data name="{key}" xml:space="preserve">
93108
<value>{translatedString}</value>
94109
</data>
95110
""");
96-
}
111+
}
97112

98-
reswBuilder.AppendLine("""
113+
reswBuilder.AppendLine("""
99114
</root>
100115
""");
101116

102-
// Write to file
103-
var outputFile = new FileInfo(Path.Combine(outFolder.FullName, $"Resources.lang-{lang}.resw"));
104-
File.WriteAllText(outputFile.FullName, reswBuilder.ToString());
105-
Console.WriteLine($"[INFO] 已生成资源文件:{outputFile.FullName}");
117+
// Write to file
118+
119+
string outputPath = lang == defaultLanguage
120+
? Path.Combine(outFolder.FullName, $"Resources.resw")
121+
: Path.Combine(outFolder.FullName, $"Resources.lang-{lang}.resw");
122+
var outputFile = new FileInfo(outputPath);
123+
File.WriteAllText(outputFile.FullName, reswBuilder.ToString());
124+
Console.WriteLine($"[INFO] 已生成资源文件:{outputFile.FullName}");
125+
}
106126
}
107127

108128

109129
// Parse a CSV file
110-
IEnumerable<StringResource> ParseCsv(FileInfo csvFile, string relativePath)
130+
IEnumerable<StringResource> ParseCsv(FileInfo csvFile, string relativePath, IEnumerable<string> languages)
111131
{
112132
using var csvFileStream = csvFile.OpenRead();
113133
IEnumerable<StringResource> lines = CsvReader.ReadFromStream(csvFileStream)
114-
.Select(line => ParseLine(line, relativePath))
134+
.Select(line => ParseLine(line, relativePath, languages))
115135
.Where(x => x is not null)!;
116136
return lines;
117137
}
118138

119139
// Parse a line in the CSV file
120-
StringResource? ParseLine(ICsvLine line, string relativePath)
140+
StringResource? ParseLine(ICsvLine line, string relativePath, IEnumerable<string> languages)
121141
{
122142
// Error checking for CSV file
123143
if (!line.HasColumn("Id") || string.IsNullOrWhiteSpace(line["Id"]))
124144
{
125-
errors.Add($"[ERROR]:at {relativePath}, Line {line.Index} : 资源Id 不能为空");
145+
Errors.Add($"[ERROR]:at {relativePath}, Line {line.Index} : 资源Id 不能为空");
126146
return null;
127147
}
128148

129149
if (!line.HasColumn("Property"))
130150
{
131-
errors.Add($"[ERROR]:at {relativePath}, Line {line.Index} : 缺少Property列");
151+
Errors.Add($"[ERROR]:at {relativePath}, Line {line.Index} : 缺少Property列");
132152
return null;
133153
}
134154

135155
if (line["Id"].StartsWith('_') && !string.IsNullOrEmpty(line["Property"]))
136156
{
137-
errors.Add($"[ERROR]:at {relativePath}, Line {line.Index} : 资源Id 标记为后台代码,但 资源属性Id 又不为空");
157+
Errors.Add($"[ERROR]:at {relativePath}, Line {line.Index} : 资源Id 标记为后台代码,但 资源属性Id 又不为空");
138158
return null;
139159
}
140160

@@ -145,13 +165,13 @@ IEnumerable<StringResource> ParseCsv(FileInfo csvFile, string relativePath)
145165
{
146166
if (!line.HasColumn(lang))
147167
{
148-
errors.Add($"[ERROR]:at {relativePath}, Line {line.Index} : 缺少语言 {lang}");
168+
Errors.Add($"[ERROR]:at {relativePath}, Line {line.Index} : 缺少语言 {lang}");
149169
return null;
150170
}
151171

152172
if (line[lang] == "")
153173
{
154-
warnings.Add($"[WARN]:at {relativePath}, Line {line.Index} : 缺少 {lang} 的翻译");
174+
Warnings.Add($"[WARN]:at {relativePath}, Line {line.Index} : 缺少 {lang} 的翻译");
155175
}
156176

157177
translations[lang] = line[lang];

0 commit comments

Comments
 (0)