@@ -23,20 +23,36 @@ public ResourcesController(IConfiguration configuration, IResourceBackend backen
2323 }
2424
2525 /// <summary>
26- /// List all resource files
26+ /// List the language columns shown in the editor. Returns one entry per
27+ /// EFFECTIVE language code across all resource groups (default file and an
28+ /// explicit culture file sharing the configured DefaultLanguageCode collapse
29+ /// into a single column), so the headers agree with the merged cells from
30+ /// <see cref="GetAllKeys"/>. A within-group default-vs-culture collision is
31+ /// surfaced via <see cref="ResourceFileInfo.HasLanguageConflict"/>; the
32+ /// legitimate cross-group case (two groups each having an "it" file) is NOT
33+ /// treated as a conflict.
2734 /// </summary>
2835 [ HttpGet ]
2936 public ActionResult < IEnumerable < ResourceFileInfo > > GetResources ( )
3037 {
3138 try
3239 {
33- var languages = _backend . Discovery . DiscoverLanguages ( _resourcePath ) ;
34- var result = languages . Select ( l => new ResourceFileInfo
40+ var directory = _backend . Discovery . DiscoverResourceGroups ( _resourcePath ) ;
41+ var allFiles = directory . Groups . SelectMany ( g => g . Files ) . ToList ( ) ;
42+ var columns = MergedLanguageColumns . Build ( allFiles ) ;
43+
44+ var result = columns . Select ( col => new ResourceFileInfo
3545 {
36- FileName = l . Name ,
37- FilePath = l . FilePath ,
38- Code = l . Code ,
39- IsDefault = l . IsDefault
46+ FileName = col . Name ,
47+ FilePath = col . WinningFilePath ,
48+ Code = col . Code ,
49+ IsDefault = col . IsDefault ,
50+ // Cross-group merge would falsely flag two legit same-code files
51+ // (e.g. CustomerResources.it + GlassResources.it). Only flag a
52+ // conflict when a SINGLE group has >1 file for this effective code.
53+ HasLanguageConflict = directory . Groups . Any ( g =>
54+ g . Files . Count ( f => MergedLanguageColumns . EffectiveCode ( f ) == col . Code ) > 1 ) ,
55+ ConflictingFilePaths = col . ConflictingFilePaths . ToList ( )
4056 } ) ;
4157 return Ok ( result ) ;
4258 }
@@ -72,16 +88,33 @@ public ActionResult<IEnumerable<ResourceKeyInfo>> GetAllKeys()
7288 . OrderBy ( k => k )
7389 . ToList ( ) ;
7490
91+ var fileByPath = group . Files . ToDictionary ( f => f . FilePath ?? string . Empty ) ;
92+ var columns = MergedLanguageColumns . Build ( group . Files ) ;
93+
7594 foreach ( var key in keys )
7695 {
7796 var values = new Dictionary < string , string ? > ( ) ;
7897 var isPlural = false ;
98+ var conflictCodes = new List < string > ( ) ;
7999
80- foreach ( var ( file , resource ) in resources )
100+ foreach ( var col in columns )
81101 {
82- var entry = resource . Entries . FirstOrDefault ( e => e . Key == key ) ;
83- values [ string . IsNullOrEmpty ( file . Code ) ? "default" : file . Code ] = entry ? . Value ;
84- if ( entry ? . IsPlural == true ) isPlural = true ;
102+ var winnerEntry = resources [ fileByPath [ col . WinningFilePath ] ] . Entries . FirstOrDefault ( e => e . Key == key ) ;
103+ var resolvedEntry = winnerEntry ;
104+
105+ // default wins; culture files fill the gap only when the winner has no value.
106+ if ( string . IsNullOrEmpty ( resolvedEntry ? . Value ) )
107+ {
108+ foreach ( var lp in col . ConflictingFilePaths )
109+ {
110+ var le = resources [ fileByPath [ lp ] ] . Entries . FirstOrDefault ( e => e . Key == key ) ;
111+ if ( ! string . IsNullOrEmpty ( le ? . Value ) ) { resolvedEntry = le ; break ; }
112+ }
113+ }
114+
115+ values [ col . Code ] = resolvedEntry ? . Value ;
116+ if ( resolvedEntry ? . IsPlural == true ) isPlural = true ;
117+ if ( col . HasConflict ) conflictCodes . Add ( col . Code ) ;
85118 }
86119
87120 var occurrenceCount = defaultResource ? . Entries . Count ( e => e . Key == key ) ?? 1 ;
@@ -93,7 +126,9 @@ public ActionResult<IEnumerable<ResourceKeyInfo>> GetAllKeys()
93126 Values = values ,
94127 OccurrenceCount = occurrenceCount ,
95128 HasDuplicates = occurrenceCount > 1 ,
96- IsPlural = isPlural
129+ IsPlural = isPlural ,
130+ HasLanguageConflict = conflictCodes . Count > 0 ,
131+ ConflictingLanguages = conflictCodes
97132 } ) ;
98133 }
99134 }
@@ -158,22 +193,49 @@ public ActionResult<ResourceKeyDetails> GetKey(string keyName, [FromQuery] strin
158193
159194 var hasDuplicates = occurrences . Count > 1 ;
160195
196+ // Merge files of this group into display columns so that the suffix-less
197+ // default file and an explicit culture file sharing the same effective code
198+ // collapse into one column (default wins; cultures only fill gaps).
199+ var byPath = resourceFiles . ToDictionary ( rf => rf . Language . FilePath ?? string . Empty ) ;
200+ var columns = MergedLanguageColumns . Build ( group . Files ) ;
201+
202+ // Resolves the i-th occurrence of keyName for a column, taking the default
203+ // winner first and falling back to a colliding culture file only when the
204+ // winner lacks that occurrence.
205+ ResourceValue ? ResolveOccurrence ( LanguageColumn col , int index )
206+ {
207+ var winnerEntries = byPath [ col . WinningFilePath ] . Entries . Where ( e => e . Key == keyName ) . ToList ( ) ;
208+ var entry = index < winnerEntries . Count ? winnerEntries [ index ] : null ;
209+
210+ if ( entry == null )
211+ {
212+ foreach ( var lp in col . ConflictingFilePaths )
213+ {
214+ var loserEntries = byPath [ lp ] . Entries . Where ( e => e . Key == keyName ) . ToList ( ) ;
215+ if ( index < loserEntries . Count ) { entry = loserEntries [ index ] ; break ; }
216+ }
217+ }
218+
219+ if ( entry == null ) return null ;
220+ return new ResourceValue
221+ {
222+ Value = entry . Value ,
223+ Comment = entry . Comment ,
224+ IsPlural = entry . IsPlural ,
225+ PluralForms = entry . PluralForms
226+ } ;
227+ }
228+
161229 // If no duplicates, return simple response
162230 if ( ! hasDuplicates )
163231 {
164232 var values = new Dictionary < string , ResourceValue > ( ) ;
165- foreach ( var file in resourceFiles )
233+ foreach ( var col in columns )
166234 {
167- var entry = file . Entries . FirstOrDefault ( e => e . Key == keyName ) ;
168- if ( entry != null )
235+ var resolved = ResolveOccurrence ( col , 0 ) ;
236+ if ( resolved != null )
169237 {
170- values [ file . Language . Code ?? "default" ] = new ResourceValue
171- {
172- Value = entry . Value ,
173- Comment = entry . Comment ,
174- IsPlural = entry . IsPlural ,
175- PluralForms = entry . PluralForms
176- } ;
238+ values [ col . Code ] = resolved ;
177239 }
178240 }
179241
@@ -192,18 +254,12 @@ public ActionResult<ResourceKeyDetails> GetKey(string keyName, [FromQuery] strin
192254 for ( int i = 0 ; i < occurrences . Count ; i ++ )
193255 {
194256 var occurrenceValues = new Dictionary < string , ResourceValue > ( ) ;
195- foreach ( var file in resourceFiles )
257+ foreach ( var col in columns )
196258 {
197- var entries = file . Entries . Where ( e => e . Key == keyName ) . ToList ( ) ;
198- if ( i < entries . Count )
259+ var resolved = ResolveOccurrence ( col , i ) ;
260+ if ( resolved != null )
199261 {
200- occurrenceValues [ file . Language . Code ?? "default" ] = new ResourceValue
201- {
202- Value = entries [ i ] . Value ,
203- Comment = entries [ i ] . Comment ,
204- IsPlural = entries [ i ] . IsPlural ,
205- PluralForms = entries [ i ] . PluralForms
206- } ;
262+ occurrenceValues [ col . Code ] = resolved ;
207263 }
208264 }
209265
@@ -216,18 +272,12 @@ public ActionResult<ResourceKeyDetails> GetKey(string keyName, [FromQuery] strin
216272
217273 // Return first occurrence in Values for backward compatibility
218274 var firstValues = new Dictionary < string , ResourceValue > ( ) ;
219- foreach ( var file in resourceFiles )
275+ foreach ( var col in columns )
220276 {
221- var entry = file . Entries . FirstOrDefault ( e => e . Key == keyName ) ;
222- if ( entry != null )
277+ var resolved = ResolveOccurrence ( col , 0 ) ;
278+ if ( resolved != null )
223279 {
224- firstValues [ file . Language . Code ?? "default" ] = new ResourceValue
225- {
226- Value = entry . Value ,
227- Comment = entry . Comment ,
228- IsPlural = entry . IsPlural ,
229- PluralForms = entry . PluralForms
230- } ;
280+ firstValues [ col . Code ] = resolved ;
231281 }
232282 }
233283
0 commit comments