77using Spectre . Console ;
88using Spectre . Console . Cli ;
99using System . ComponentModel ;
10- using System . Text . Json ;
1110
1211namespace LocalizationManager . Commands . Cloud ;
1312
@@ -36,16 +35,6 @@ public class PullCommandSettings : BaseCommandSettings
3635 [ DefaultValue ( "prompt" ) ]
3736 public string Strategy { get ; set ; } = "prompt" ;
3837
39- [ CommandOption ( "--config-only" ) ]
40- [ Description ( "Pull only configuration (lrm.json), skip resources" ) ]
41- [ DefaultValue ( false ) ]
42- public bool ConfigOnly { get ; set ; }
43-
44- [ CommandOption ( "--resources-only" ) ]
45- [ Description ( "Pull only resources, skip configuration" ) ]
46- [ DefaultValue ( false ) ]
47- public bool ResourcesOnly { get ; set ; }
48-
4938 [ CommandOption ( "--include-unapproved" ) ]
5039 [ Description ( "Include translations that haven't been approved yet (when project has review workflow enabled)" ) ]
5140 [ DefaultValue ( false ) ]
@@ -95,13 +84,6 @@ public override int Execute(CommandContext context, PullCommandSettings settings
9584 AnsiConsole . MarkupLine ( $ "[dim]Remote: { remoteUrl ! . ToString ( ) . EscapeMarkup ( ) } [/]") ;
9685 AnsiConsole . WriteLine ( ) ;
9786
98- // Validate conflicting options
99- if ( settings . ConfigOnly && settings . ResourcesOnly )
100- {
101- AnsiConsole . MarkupLine ( "[red]✗ Cannot use --config-only and --resources-only together![/]" ) ;
102- return 1 ;
103- }
104-
10587 // Parse resolution strategy
10688 var ( strategy , isValidStrategy ) = ParseStrategy ( settings . Strategy ) ;
10789 if ( ! isValidStrategy )
@@ -154,10 +136,9 @@ public override int Execute(CommandContext context, PullCommandSettings settings
154136 return 1 ;
155137 }
156138
157- // Load local config if exists
139+ // Load local config if exists for validation
158140 ConfigurationModel ? localConfig = null ;
159- var localConfigPath = Path . Combine ( projectDirectory , "lrm.json" ) ;
160- if ( File . Exists ( localConfigPath ) )
141+ if ( File . Exists ( Path . Combine ( projectDirectory , "lrm.json" ) ) )
161142 {
162143 localConfig = Core . Configuration . ConfigurationManager . LoadConfigurationAsync ( projectDirectory , cancellationToken ) . GetAwaiter ( ) . GetResult ( ) ;
163144 }
@@ -203,16 +184,25 @@ public override int Execute(CommandContext context, PullCommandSettings settings
203184 AnsiConsole . MarkupLine ( "[yellow]⚠ Upgrading from file-based sync to key-level sync[/]" ) ;
204185 }
205186
206- // Get backend for the format
187+ // Get backend for the local format (client-agnostic: format is determined locally)
207188 var backendFactory = new Core . Backends . ResourceBackendFactory ( ) ;
208189 Core . Abstractions . IResourceBackend backend ;
209190 try
210191 {
211- backend = backendFactory . GetBackend ( remoteProject ! . Format , config ) ;
192+ // Use local config format or auto-detect from files
193+ if ( ! string . IsNullOrEmpty ( config . ResourceFormat ) )
194+ {
195+ backend = backendFactory . GetBackend ( config . ResourceFormat , config ) ;
196+ }
197+ else
198+ {
199+ backend = backendFactory . ResolveFromPath ( projectDirectory , config ) ;
200+ }
212201 }
213- catch ( NotSupportedException )
202+ catch ( NotSupportedException ex )
214203 {
215- AnsiConsole . MarkupLine ( $ "[red]✗ Unsupported format: { remoteProject . Format } [/]") ;
204+ AnsiConsole . MarkupLine ( $ "[red]✗ { ex . Message } [/]") ;
205+ AnsiConsole . MarkupLine ( "[dim]Set 'format' in lrm.json or ensure resource files exist.[/]" ) ;
216206 return 1 ;
217207 }
218208
@@ -243,36 +233,33 @@ public override int Execute(CommandContext context, PullCommandSettings settings
243233 AnsiConsole . MarkupLine ( $ "[dim]Remote: { pullResponse . Total } entries[/]") ;
244234
245235 // Perform three-way merge
236+ // Pass default language to normalize API language codes to CLI convention
237+ // (CLI uses "" for default language, API uses actual language code like "en")
238+ // But XLIFF/iOS/i18next use explicit language codes, so don't normalize for those
246239 var merger = new KeyLevelMerger ( ) ;
247240 MergeResult mergeResult ;
248241
242+ // Determine if we should normalize default language to ""
243+ // If we have local entries, check if they use "" for default
244+ // Otherwise, use the backend's convention
245+ var normalizeDefaultLang = localEntries . Any ( )
246+ ? localEntries . Any ( e => string . IsNullOrEmpty ( e . Lang ) )
247+ : KeyLevelMerger . BackendUsesEmptyForDefault ( backend . Name ) ;
248+
249249 if ( syncState == null || ! syncState . Entries . Any ( ) )
250250 {
251251 // First pull - accept all remote
252- mergeResult = merger . MergeForFirstPull ( pullResponse . Entries ) ;
252+ mergeResult = merger . MergeForFirstPull ( pullResponse . Entries , pullResponse . DefaultLanguage , normalizeDefaultLang ) ;
253253 AnsiConsole . MarkupLine ( "[dim]First pull - accepting all remote entries[/]" ) ;
254254 }
255255 else
256256 {
257- // Normal merge
258- mergeResult = merger . MergeForPull ( localEntries , pullResponse . Entries , syncState ) ;
259- }
260-
261- // Handle config merge
262- ConfigMergeResult ? configMergeResult = null ;
263- if ( ! settings . ResourcesOnly && pullResponse . Config != null )
264- {
265- var configMerger = new ConfigMerger ( ) ;
266- var localConfigJson = File . Exists ( localConfigPath ) ? File . ReadAllText ( localConfigPath ) : null ;
267- var localProps = localConfigJson != null
268- ? configMerger . ExtractConfigProperties ( localConfigJson )
269- : new Dictionary < string , ( string Value , string Hash ) > ( ) ;
270-
271- configMergeResult = configMerger . MergeForPull ( localProps , pullResponse . Config , syncState ) ;
257+ // Normal merge - uses local entries to detect convention internally
258+ mergeResult = merger . MergeForPull ( localEntries , pullResponse . Entries , syncState , pullResponse . DefaultLanguage ) ;
272259 }
273260
274261 // Show changes summary
275- ShowMergeSummary ( mergeResult , configMergeResult , settings ) ;
262+ ShowMergeSummary ( mergeResult ) ;
276263
277264 // Handle conflicts
278265 if ( mergeResult . HasConflicts )
@@ -327,7 +314,7 @@ public override int Execute(CommandContext context, PullCommandSettings settings
327314 }
328315
329316 // Check if there's anything to do
330- if ( mergeResult . ToWrite . Count == 0 && configMergeResult ? . ToWrite . Count == 0 )
317+ if ( mergeResult . ToWrite . Count == 0 )
331318 {
332319 AnsiConsole . MarkupLine ( "[green]✓ Already up to date![/]" ) ;
333320 return 0 ;
@@ -364,30 +351,10 @@ public override int Execute(CommandContext context, PullCommandSettings settings
364351 // Apply changes
365352 try
366353 {
367- // Update config FIRST so backend uses correct settings for file regeneration
368- if ( ! settings . ResourcesOnly && configMergeResult ? . ToWrite . Count > 0 )
369- {
370- AnsiConsole . Status ( )
371- . Start ( "Updating configuration..." , ctx =>
372- {
373- var configMerger = new ConfigMerger ( ) ;
374- var localConfigJson = File . Exists ( localConfigPath ) ? File . ReadAllText ( localConfigPath ) : "{}" ;
375- var newConfigJson = configMerger . ApplyConfigChanges ( localConfigJson , configMergeResult . ToWrite ) ;
376- File . WriteAllText ( localConfigPath , newConfigJson ) ;
377- } ) ;
378-
379- // Reload config and backend after config update
380- config = Core . Configuration . ConfigurationManager . LoadConfigurationAsync ( projectDirectory , cancellationToken ) . GetAwaiter ( ) . GetResult ( ) ;
381- backend = backendFactory . GetBackend ( remoteProject ! . Format , config ) ;
382- // Re-discover languages with updated config
383- languages = backend . Discovery . DiscoverLanguages ( projectDirectory ) ;
384- }
385-
386354 AnsiConsole . Status ( )
387355 . Start ( "Applying changes..." , ctx =>
388356 {
389- // Update resources (now with correct backend from updated config)
390- if ( ! settings . ConfigOnly && mergeResult . ToWrite . Count > 0 )
357+ if ( mergeResult . ToWrite . Count > 0 )
391358 {
392359 ctx . Status ( "Regenerating resource files..." ) ;
393360 var regenerator = new FileRegenerator ( backend , projectDirectory ) ;
@@ -404,7 +371,7 @@ public override int Execute(CommandContext context, PullCommandSettings settings
404371 } ) ;
405372
406373 // Update sync state
407- var newSyncState = BuildNewSyncState ( mergeResult , configMergeResult , localEntries ) ;
374+ var newSyncState = BuildNewSyncState ( mergeResult , localEntries ) ;
408375 SyncStateManager . SaveAsync ( projectDirectory , newSyncState , cancellationToken ) . GetAwaiter ( ) . GetResult ( ) ;
409376
410377 AnsiConsole . WriteLine ( ) ;
@@ -418,10 +385,6 @@ public override int Execute(CommandContext context, PullCommandSettings settings
418385 {
419386 AnsiConsole . MarkupLine ( $ " [dim]Auto-merged: { mergeResult . AutoMerged } entries[/]") ;
420387 }
421- if ( configMergeResult ? . ToWrite . Count > 0 )
422- {
423- AnsiConsole . MarkupLine ( $ " [dim]Config properties: { configMergeResult . ToWrite . Count } [/]") ;
424- }
425388
426389 if ( backupPath != null )
427390 {
@@ -471,7 +434,7 @@ public override int Execute(CommandContext context, PullCommandSettings settings
471434 }
472435 }
473436
474- private void ShowMergeSummary ( MergeResult mergeResult , ConfigMergeResult ? configMergeResult , PullCommandSettings settings )
437+ private void ShowMergeSummary ( MergeResult mergeResult )
475438 {
476439 AnsiConsole . MarkupLine ( "[blue]Merge summary:[/]" ) ;
477440
@@ -487,14 +450,6 @@ private void ShowMergeSummary(MergeResult mergeResult, ConfigMergeResult? config
487450 {
488451 AnsiConsole . MarkupLine ( $ " [yellow]! { mergeResult . Conflicts . Count } conflict(s) need resolution[/]") ;
489452 }
490- if ( configMergeResult ? . ToWrite . Count > 0 )
491- {
492- AnsiConsole . MarkupLine ( $ " [blue]~ { configMergeResult . ToWrite . Count } config property(ies) to update[/]") ;
493- }
494- if ( configMergeResult ? . Conflicts . Count > 0 )
495- {
496- AnsiConsole . MarkupLine ( $ " [yellow]! { configMergeResult . Conflicts . Count } config conflict(s)[/]") ;
497- }
498453
499454 AnsiConsole . WriteLine ( ) ;
500455 }
@@ -643,14 +598,13 @@ private bool ResolveConflictsInteractively(KeyLevelMerger merger, MergeResult me
643598 } ;
644599 }
645600
646- private SyncState BuildNewSyncState ( MergeResult mergeResult , ConfigMergeResult ? configMergeResult , List < LocalEntry > localEntries )
601+ private SyncState BuildNewSyncState ( MergeResult mergeResult , List < LocalEntry > localEntries )
647602 {
648603 var newState = new SyncState
649604 {
650605 Version = 2 ,
651606 Timestamp = DateTime . UtcNow ,
652- Entries = new Dictionary < string , Dictionary < string , string > > ( ) ,
653- ConfigProperties = new Dictionary < string , string > ( )
607+ Entries = new Dictionary < string , Dictionary < string , string > > ( )
654608 } ;
655609
656610 // Add hashes from merged entries
@@ -686,15 +640,6 @@ private SyncState BuildNewSyncState(MergeResult mergeResult, ConfigMergeResult?
686640 }
687641 }
688642
689- // Add config hashes
690- if ( configMergeResult != null )
691- {
692- foreach ( var ( path , hash ) in configMergeResult . NewHashes )
693- {
694- newState . ConfigProperties [ path ] = hash ;
695- }
696- }
697-
698643 return newState ;
699644 }
700645}
0 commit comments