Skip to content

Commit f509b46

Browse files
committed
Rewrite localizer
1 parent 845e3d3 commit f509b46

File tree

5 files changed

+200
-248
lines changed

5 files changed

+200
-248
lines changed

Scripts/LocalizerScript/LocalizerScript.csproj renamed to FluentLauncher.Infra.Localizer/FluentLauncher.Infra.Localizer.csproj

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
44
<OutputType>Exe</OutputType>
55
<TargetFramework>net8.0</TargetFramework>
6-
<ImplicitUsings>enable</ImplicitUsings>
7-
<Nullable>disable</Nullable>
8-
<BaseOutputPath>..\bin</BaseOutputPath>
9-
<Platforms>AnyCPU;x64</Platforms>
6+
<Nullable>enable</Nullable>
107
</PropertyGroup>
118

129
<ItemGroup>
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
using Csv;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Data;
5+
using System.IO;
6+
using System.Linq;
7+
using System.Text;
8+
9+
// Input arguments
10+
DirectoryInfo srcFolder = new(args[0]);
11+
DirectoryInfo outFolder = new(args[1]);
12+
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)
27+
{
28+
strings[lang] = new();
29+
}
30+
31+
// Enumerate and parse all CSV files
32+
foreach (FileInfo file in srcFolder.EnumerateFiles("*.csv", SearchOption.AllDirectories))
33+
{
34+
string relativePath = Path.GetRelativePath(srcFolder.FullName, file.FullName);
35+
foreach (var str in ParseCsv(file, relativePath))
36+
{
37+
foreach (string lang in languages)
38+
{
39+
string resourceId = relativePath[0..^".csv".Length].Replace(Path.DirectorySeparatorChar, '_') + "_" + str.GetName();
40+
strings[lang][resourceId] = str.Translations[lang];
41+
}
42+
}
43+
44+
}
45+
46+
// Print warnings (missing translations)
47+
Console.ForegroundColor = ConsoleColor.Yellow;
48+
49+
foreach (var item in warnings)
50+
Console.WriteLine(item);
51+
52+
// Print errors (invalid CSV files)
53+
Console.ForegroundColor = ConsoleColor.Red;
54+
55+
foreach (var item in errors)
56+
Console.WriteLine(item);
57+
58+
if (errors.Count > 0)
59+
Environment.Exit(-1);
60+
61+
Console.ForegroundColor = ConsoleColor.Green;
62+
63+
// Generate .resw files
64+
if (!Directory.Exists(outFolder.FullName))
65+
Directory.CreateDirectory(outFolder.FullName);
66+
67+
foreach (string lang in languages)
68+
{
69+
// Build .resw file
70+
var reswBuilder = new StringBuilder();
71+
72+
reswBuilder.AppendLine("""
73+
<?xml version="1.0" encoding="utf-8"?>
74+
<root>
75+
<resheader name="resmimetype">
76+
<value>text/microsoft-resx</value>
77+
</resheader>
78+
<resheader name="version">
79+
<value>2.0</value>
80+
</resheader>
81+
<resheader name="reader">
82+
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
83+
</resheader>
84+
<resheader name="writer">
85+
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
86+
</resheader>
87+
""");
88+
89+
foreach ((string key, string translatedString) in strings[lang])
90+
{
91+
reswBuilder.AppendLine($"""
92+
<data name="{key}" xml:space="preserve">
93+
<value>{translatedString}</value>
94+
</data>
95+
""");
96+
}
97+
98+
reswBuilder.AppendLine("""
99+
</root>
100+
""");
101+
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}");
106+
}
107+
108+
109+
// Parse a CSV file
110+
IEnumerable<StringResource> ParseCsv(FileInfo csvFile, string relativePath)
111+
{
112+
using var csvFileStream = csvFile.OpenRead();
113+
IEnumerable<StringResource> lines = CsvReader.ReadFromStream(csvFileStream)
114+
.Select(line => ParseLine(line, relativePath))
115+
.Where(x => x is not null)!;
116+
return lines;
117+
}
118+
119+
// Parse a line in the CSV file
120+
StringResource? ParseLine(ICsvLine line, string relativePath)
121+
{
122+
// Error checking for CSV file
123+
if (!line.HasColumn("Id") || string.IsNullOrWhiteSpace(line["Id"]))
124+
{
125+
errors.Add($"[ERROR]:at {relativePath}, Line {line.Index} : 资源Id 不能为空");
126+
return null;
127+
}
128+
129+
if (!line.HasColumn("Property"))
130+
{
131+
errors.Add($"[ERROR]:at {relativePath}, Line {line.Index} : 缺少Property列");
132+
return null;
133+
}
134+
135+
if (line["Id"].StartsWith('_') && !string.IsNullOrEmpty(line["Property"]))
136+
{
137+
errors.Add($"[ERROR]:at {relativePath}, Line {line.Index} : 资源Id 标记为后台代码,但 资源属性Id 又不为空");
138+
return null;
139+
}
140+
141+
// Parse translations
142+
Dictionary<string, string> translations = new();
143+
144+
foreach (string lang in languages)
145+
{
146+
if (!line.HasColumn(lang))
147+
{
148+
errors.Add($"[ERROR]:at {relativePath}, Line {line.Index} : 缺少语言 {lang}");
149+
return null;
150+
}
151+
152+
if (line[lang] == "")
153+
{
154+
warnings.Add($"[WARN]:at {relativePath}, Line {line.Index} : 缺少 {lang} 的翻译");
155+
}
156+
157+
translations[lang] = line[lang];
158+
}
159+
160+
var resource = new StringResource
161+
{
162+
Uid = line["Id"],
163+
Property = line["Property"],
164+
Translations = translations
165+
};
166+
167+
return resource;
168+
}
169+
170+
171+
/// <summary>
172+
/// Represents a string resource with translations for different languages
173+
/// </summary>
174+
record class StringResource
175+
{
176+
/// <summary>
177+
/// x:Uid of the component (if used in XAML) or ID of the resource (if used in code behind)
178+
/// </summary>
179+
public required string Uid { get; init; }
180+
181+
/// <summary>
182+
/// Property name of the component (if used in XAML)
183+
/// </summary>
184+
public required string Property { get; init; }
185+
186+
/// <summary>
187+
/// Translations for different languages (key=language code, value=translated string)
188+
/// </summary>
189+
public required Dictionary<string, string> Translations { get; init; }
190+
191+
public string GetName()
192+
{
193+
if (Uid.StartsWith('_'))
194+
return Uid;
195+
196+
return $"{Uid}.{Property}";
197+
}
198+
}

Scripts/LocalizerScript.sln

Lines changed: 0 additions & 31 deletions
This file was deleted.

0 commit comments

Comments
 (0)