11using Csv ;
22using System ;
33using System . Collections . Generic ;
4+ using System . CommandLine ;
5+ using System . CommandLine . Parsing ;
46using System . Data ;
57using System . IO ;
68using System . Linq ;
79using 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">
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