1
+ using System . Text . RegularExpressions ;
2
+
3
+ using SharedTools ;
4
+
5
+ // ReSharper disable ClassNeverInstantiated.Global
6
+ // ReSharper disable UnusedType.Global
7
+ // ReSharper disable UnusedMember.Global
8
+
9
+ namespace Build . Commands ;
10
+
11
+ public sealed partial class CollectI18NKeysCommand
12
+ {
13
+ [ Command ( "collect-i18n" , Description = "Collect I18N keys" ) ]
14
+ public async Task CollectI18NKeys ( )
15
+ {
16
+ if ( ! Environment . IsWorkingDirectoryValid ( ) )
17
+ return ;
18
+
19
+ Console . WriteLine ( "=========================" ) ;
20
+ Console . Write ( "- Collecting I18N keys ..." ) ;
21
+
22
+ var cwd = Environment . GetAIStudioDirectory ( ) ;
23
+ var binPath = Path . Join ( cwd , "bin" ) ;
24
+ var objPath = Path . Join ( cwd , "obj" ) ;
25
+ var wwwrootPath = Path . Join ( cwd , "wwwroot" ) ;
26
+ var allFiles = Directory . EnumerateFiles ( cwd , "*" , SearchOption . AllDirectories ) ;
27
+ var counter = 0 ;
28
+ var sb = new StringBuilder ( ) ;
29
+
30
+ foreach ( var filePath in allFiles )
31
+ {
32
+ counter ++ ;
33
+ if ( filePath . StartsWith ( binPath , StringComparison . OrdinalIgnoreCase ) )
34
+ continue ;
35
+
36
+ if ( filePath . StartsWith ( objPath , StringComparison . OrdinalIgnoreCase ) )
37
+ continue ;
38
+
39
+ if ( filePath . StartsWith ( wwwrootPath , StringComparison . OrdinalIgnoreCase ) )
40
+ continue ;
41
+
42
+ var content = await File . ReadAllTextAsync ( filePath , Encoding . UTF8 ) ;
43
+ var matches = this . FindAllTextTags ( content ) ;
44
+ if ( matches . Count == 0 )
45
+ continue ;
46
+
47
+ var ns = this . DetermineNamespace ( filePath ) ;
48
+ var fileInfo = new FileInfo ( filePath ) ;
49
+ var name = fileInfo . Name . Replace ( fileInfo . Extension , string . Empty ) ;
50
+ var langNamespace = $ "{ ns } ::{ name } ". ToUpperInvariant ( ) . Replace ( "." , "::" ) ;
51
+ foreach ( var match in matches )
52
+ {
53
+ var key = $ "root::{ langNamespace } ::T{ match . ToFNV32 ( ) } ";
54
+
55
+ }
56
+ }
57
+
58
+ Console . WriteLine ( $ " { counter : ###,###} files processed.") ;
59
+ Console . WriteLine ( ) ;
60
+ }
61
+
62
+ private List < string > FindAllTextTags ( ReadOnlySpan < char > fileContent )
63
+ {
64
+ const string START_TAG = """
65
+ T("
66
+ """ ;
67
+
68
+ const string END_TAG = """
69
+ ")
70
+ """ ;
71
+
72
+ var matches = new List < string > ( ) ;
73
+ var startIdx = fileContent . IndexOf ( START_TAG ) ;
74
+ var content = fileContent ;
75
+ while ( startIdx > - 1 )
76
+ {
77
+ content = content [ ( startIdx + START_TAG . Length ) ..] ;
78
+ var endIdx = content . IndexOf ( END_TAG ) ;
79
+ if ( endIdx == - 1 )
80
+ break ;
81
+
82
+ var match = content [ ..endIdx ] ;
83
+ matches . Add ( match . ToString ( ) ) ;
84
+
85
+ startIdx = content . IndexOf ( START_TAG ) ;
86
+ }
87
+
88
+ return matches ;
89
+ }
90
+
91
+ private string ? DetermineNamespace ( string filePath )
92
+ {
93
+ // Is it a C# file? Then we can read the namespace from it:
94
+ if ( filePath . EndsWith ( ".cs" , StringComparison . OrdinalIgnoreCase ) )
95
+ return this . ReadNamespaceFromCSharp ( filePath ) ;
96
+
97
+ // Is it a Razor file? Then, it depends:
98
+ if ( filePath . EndsWith ( ".razor" , StringComparison . OrdinalIgnoreCase ) )
99
+ {
100
+ // Check if the file contains a namespace declaration:
101
+ var blazorNamespace = this . ReadNamespaceFromRazor ( filePath ) ;
102
+ if ( blazorNamespace != null )
103
+ return blazorNamespace ;
104
+
105
+ // Alright, no namespace declaration. Let's check the corresponding C# file:
106
+ var csFilePath = $ "{ filePath } .cs";
107
+ if ( File . Exists ( csFilePath ) )
108
+ {
109
+ var csNamespace = this . ReadNamespaceFromCSharp ( csFilePath ) ;
110
+ if ( csNamespace != null )
111
+ return csNamespace ;
112
+
113
+ Console . WriteLine ( $ "- Error: Neither the blazor file '{ filePath } ' nor the corresponding C# file '{ csFilePath } ' contain a namespace declaration.") ;
114
+ return null ;
115
+ }
116
+
117
+ Console . WriteLine ( $ "- Error: The blazor file '{ filePath } ' does not contain a namespace declaration and the corresponding C# file '{ csFilePath } ' does not exist.") ;
118
+ return null ;
119
+ }
120
+
121
+ // Not a C# or Razor file. We can't determine the namespace:
122
+ Console . WriteLine ( $ "- Error: The file '{ filePath } ' is neither a C# nor a Razor file. We can't determine the namespace.") ;
123
+ return null ;
124
+ }
125
+
126
+ private string ? ReadNamespaceFromCSharp ( string filePath )
127
+ {
128
+ var content = File . ReadAllText ( filePath , Encoding . UTF8 ) ;
129
+ var matches = CSharpNamespaceRegex ( ) . Matches ( content ) ;
130
+
131
+ if ( matches . Count == 0 )
132
+ return null ;
133
+
134
+ if ( matches . Count > 1 )
135
+ {
136
+ Console . WriteLine ( $ "The file '{ filePath } ' contains multiple namespaces. This scenario is not supported.") ;
137
+ return null ;
138
+ }
139
+
140
+ var match = matches [ 0 ] ;
141
+ return match . Groups [ 1 ] . Value ;
142
+ }
143
+
144
+ private string ? ReadNamespaceFromRazor ( string filePath )
145
+ {
146
+ var content = File . ReadAllText ( filePath , Encoding . UTF8 ) ;
147
+ var matches = BlazorNamespaceRegex ( ) . Matches ( content ) ;
148
+
149
+ if ( matches . Count == 0 )
150
+ return null ;
151
+
152
+ if ( matches . Count > 1 )
153
+ {
154
+ Console . WriteLine ( $ "The file '{ filePath } ' contains multiple namespaces. This scenario is not supported.") ;
155
+ return null ;
156
+ }
157
+
158
+ var match = matches [ 0 ] ;
159
+ return match . Groups [ 1 ] . Value ;
160
+ }
161
+
162
+ [ GeneratedRegex ( """@namespace\s+([a-zA-Z0-9_.]+)""" ) ]
163
+ private static partial Regex BlazorNamespaceRegex ( ) ;
164
+
165
+ [ GeneratedRegex ( """namespace\s+([a-zA-Z0-9_.]+)""" ) ]
166
+ private static partial Regex CSharpNamespaceRegex ( ) ;
167
+ }
0 commit comments