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+ }
0 commit comments