Skip to content

Commit 5326142

Browse files
committed
Fix drawer isolation and translation source text handling
- Add Clone() and ApplyFrom() methods to TranslationGridRow - Drawer now works on cloned row, changes apply only on 'Apply' click - Pass current UI source texts to translation API for unsaved edits - Support plural forms in translation result application
1 parent 0fd79fe commit 5326142

3 files changed

Lines changed: 168 additions & 26 deletions

File tree

cloud/src/LrmCloud.Web/Components/TranslateDialog.razor

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,13 @@
177177
[Parameter]
178178
public List<string> KeysToTranslate { get; set; } = new();
179179

180+
/// <summary>
181+
/// Optional source texts for each key. When provided, these are used instead of database values.
182+
/// This allows translating unsaved edits from the UI.
183+
/// </summary>
184+
[Parameter]
185+
public Dictionary<string, string>? SourceTexts { get; set; }
186+
180187
private List<TranslationProviderDto> _providers = new();
181188
private string? _selectedProvider;
182189
private HashSet<string> _selectedLanguages = new();
@@ -296,7 +303,8 @@
296303
OnlyMissing = _onlyMissing,
297304
Overwrite = _overwrite,
298305
Context = _context,
299-
SaveToDatabase = false // Return translations for UI preview, don't save yet
306+
SaveToDatabase = false, // Return translations for UI preview, don't save yet
307+
SourceTexts = SourceTexts // Pass current UI values if provided
300308
};
301309

302310
_translationResult = await TranslationService.TranslateKeysAsync(ProjectId, request);

cloud/src/LrmCloud.Web/Models/TranslationGridRow.cs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,81 @@ public void SetTranslation(string languageCode, string? value, int? translationI
130130
};
131131
}
132132
}
133+
134+
/// <summary>
135+
/// Creates a deep clone of this row for isolated editing.
136+
/// </summary>
137+
public TranslationGridRow Clone()
138+
{
139+
var clone = new TranslationGridRow
140+
{
141+
KeyId = KeyId,
142+
KeyName = KeyName,
143+
KeyPath = KeyPath,
144+
IsPlural = IsPlural,
145+
Comment = Comment,
146+
Version = Version,
147+
UpdatedAt = UpdatedAt,
148+
Translations = new Dictionary<string, TranslationCell>()
149+
};
150+
151+
foreach (var kvp in Translations)
152+
{
153+
clone.Translations[kvp.Key] = new TranslationCell
154+
{
155+
TranslationId = kvp.Value.TranslationId,
156+
LanguageCode = kvp.Value.LanguageCode,
157+
Value = kvp.Value.Value,
158+
OriginalValue = kvp.Value.OriginalValue,
159+
PluralForm = kvp.Value.PluralForm,
160+
Status = kvp.Value.Status,
161+
Version = kvp.Value.Version,
162+
IsDirty = kvp.Value.IsDirty
163+
};
164+
}
165+
166+
return clone;
167+
}
168+
169+
/// <summary>
170+
/// Copies values from another row into this row (for applying edits from a clone).
171+
/// </summary>
172+
public void ApplyFrom(TranslationGridRow source)
173+
{
174+
Comment = source.Comment;
175+
IsPlural = source.IsPlural;
176+
177+
// Copy translations
178+
foreach (var kvp in source.Translations)
179+
{
180+
if (Translations.TryGetValue(kvp.Key, out var existingCell))
181+
{
182+
existingCell.Value = kvp.Value.Value;
183+
existingCell.IsDirty = kvp.Value.IsDirty;
184+
}
185+
else
186+
{
187+
Translations[kvp.Key] = new TranslationCell
188+
{
189+
TranslationId = kvp.Value.TranslationId,
190+
LanguageCode = kvp.Value.LanguageCode,
191+
Value = kvp.Value.Value,
192+
OriginalValue = kvp.Value.OriginalValue,
193+
PluralForm = kvp.Value.PluralForm,
194+
Status = kvp.Value.Status,
195+
Version = kvp.Value.Version,
196+
IsDirty = kvp.Value.IsDirty
197+
};
198+
}
199+
}
200+
201+
// Remove translations that were removed in source
202+
var keysToRemove = Translations.Keys.Where(k => !source.Translations.ContainsKey(k)).ToList();
203+
foreach (var key in keysToRemove)
204+
{
205+
Translations.Remove(key);
206+
}
207+
}
133208
}
134209

135210
/// <summary>

cloud/src/LrmCloud.Web/Pages/Projects/Editor.razor

Lines changed: 84 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,8 @@ else
179179
private bool _isSaving;
180180
private bool _hasUnsavedChanges;
181181
private bool _drawerOpen;
182-
private TranslationGridRow? _selectedRow;
182+
private TranslationGridRow? _selectedRow; // Clone for drawer editing (isolated)
183+
private TranslationGridRow? _originalSelectedRow; // Original row reference (in grid)
183184
private AddKeyDialog? _addKeyDialog;
184185
private ConfirmDeleteDialog? _deleteDialog;
185186
private IEnumerable<TranslationGridRow>? _pendingDeleteRows;
@@ -519,14 +520,16 @@ else
519520

520521
private void OnRowSelected(TranslationGridRow row)
521522
{
522-
_selectedRow = row;
523+
_originalSelectedRow = row;
524+
_selectedRow = row.Clone(); // Drawer works on a clone to isolate changes
523525
_drawerOpen = true;
524526
}
525527

526528
private void CloseDrawer()
527529
{
528530
_drawerOpen = false;
529531
_selectedRow = null;
532+
_originalSelectedRow = null;
530533
}
531534

532535
private void OnCellChanged((TranslationGridRow Row, string LanguageCode, string Value) args)
@@ -554,12 +557,17 @@ else
554557
if (_project == null) return;
555558

556559
var rowList = rows.ToList();
560+
561+
// Build source texts from current UI state (may include unsaved edits)
562+
var sourceTexts = BuildSourceTextsFromRows(rowList, _project.DefaultLanguage);
563+
557564
var parameters = new DialogParameters
558565
{
559566
{ nameof(TranslateDialog.ProjectId), ProjectId },
560567
{ nameof(TranslateDialog.SourceLanguage), _project.DefaultLanguage },
561568
{ nameof(TranslateDialog.AvailableLanguages), _languages },
562-
{ nameof(TranslateDialog.KeysToTranslate), rowList.Select(r => r.KeyName).ToList() }
569+
{ nameof(TranslateDialog.KeysToTranslate), rowList.Select(r => r.KeyName).ToList() },
570+
{ nameof(TranslateDialog.SourceTexts), sourceTexts }
563571
};
564572

565573
var options = new DialogOptions
@@ -582,6 +590,7 @@ else
582590

583591
/// <summary>
584592
/// Applies translation results to the UI state, marking cells as dirty so Save button enables.
593+
/// Handles both non-plural and plural keys.
585594
/// </summary>
586595
private void ApplyTranslationsToUI(List<TranslationGridRow> rows, TranslateResponseDto translationResult)
587596
{
@@ -595,16 +604,22 @@ else
595604
continue;
596605
}
597606

598-
if (!row.Translations.TryGetValue(translationRes.TargetLanguage, out var cell))
607+
// Build the cell key: "lang" for non-plural, "lang:pluralForm" for plural
608+
var cellKey = string.IsNullOrEmpty(translationRes.PluralForm)
609+
? translationRes.TargetLanguage
610+
: TranslationGridRow.GetKey(translationRes.TargetLanguage, translationRes.PluralForm);
611+
612+
if (!row.Translations.TryGetValue(cellKey, out var cell))
599613
{
600-
// Create new cell for this language
614+
// Create new cell for this language/plural form
601615
cell = new TranslationCell
602616
{
603617
LanguageCode = translationRes.TargetLanguage,
618+
PluralForm = translationRes.PluralForm,
604619
Value = null,
605620
OriginalValue = null
606621
};
607-
row.Translations[translationRes.TargetLanguage] = cell;
622+
row.Translations[cellKey] = cell;
608623
}
609624

610625
// Apply translated text and mark as dirty
@@ -667,10 +682,16 @@ else
667682
// Stage changes in the editor (don't save to DB yet)
668683
// User must click the main Save button to persist with optional snapshot
669684
670-
// Ensure row is tracked in edited rows
671-
if (!_editedRows.ContainsKey(row.KeyName))
685+
// Apply changes from the drawer's clone to the original row in the grid
686+
if (_originalSelectedRow != null)
672687
{
673-
_editedRows[row.KeyName] = row;
688+
_originalSelectedRow.ApplyFrom(row);
689+
690+
// Track the original row (not the clone) in edited rows
691+
if (!_editedRows.ContainsKey(_originalSelectedRow.KeyName))
692+
{
693+
_editedRows[_originalSelectedRow.KeyName] = _originalSelectedRow;
694+
}
674695
}
675696

676697
// Mark that we have unsaved changes
@@ -679,6 +700,7 @@ else
679700
// Close the drawer
680701
_drawerOpen = false;
681702
_selectedRow = null;
703+
_originalSelectedRow = null;
682704

683705
Snackbar.Add("Changes staged. Click Save to persist.", Severity.Info);
684706
}
@@ -687,12 +709,16 @@ else
687709
{
688710
if (_project == null) return;
689711

712+
// Build source texts from current UI state (may include unsaved edits)
713+
var sourceTexts = BuildSourceTextsFromRows(new List<TranslationGridRow> { row }, _project.DefaultLanguage);
714+
690715
var parameters = new DialogParameters
691716
{
692717
{ nameof(TranslateDialog.ProjectId), ProjectId },
693718
{ nameof(TranslateDialog.SourceLanguage), _project.DefaultLanguage },
694719
{ nameof(TranslateDialog.AvailableLanguages), _languages },
695-
{ nameof(TranslateDialog.KeysToTranslate), new List<string> { row.KeyName } }
720+
{ nameof(TranslateDialog.KeysToTranslate), new List<string> { row.KeyName } },
721+
{ nameof(TranslateDialog.SourceTexts), sourceTexts }
696722
};
697723

698724
var options = new DialogOptions
@@ -717,26 +743,26 @@ else
717743
/// <summary>
718744
/// Applies translation results to the currently selected drawer row.
719745
/// Unlike ApplyTranslationsToUI, this does NOT stage to _editedRows - the user must click Apply.
746+
/// Handles both non-plural and plural keys.
720747
/// </summary>
721748
private void ApplyTranslationsToDrawerRow(TranslationGridRow row, TranslateResponseDto translationResult)
722749
{
723750
var appliedCount = 0;
724751

725752
foreach (var translationRes in translationResult.Results.Where(r => r.Success && r.Key == row.KeyName))
726753
{
727-
// For non-plural keys, use language code directly
728-
// For plural keys, the API currently doesn't return plural form, so default to "other"
729-
var cellKey = row.IsPlural
730-
? TranslationGridRow.GetKey(translationRes.TargetLanguage, PluralForms.Other)
731-
: translationRes.TargetLanguage;
754+
// Build the cell key: "lang" for non-plural, "lang:pluralForm" for plural
755+
var cellKey = string.IsNullOrEmpty(translationRes.PluralForm)
756+
? translationRes.TargetLanguage
757+
: TranslationGridRow.GetKey(translationRes.TargetLanguage, translationRes.PluralForm);
732758

733759
if (!row.Translations.TryGetValue(cellKey, out var cell))
734760
{
735-
// Create new cell for this language
761+
// Create new cell for this language/plural form
736762
cell = new TranslationCell
737763
{
738764
LanguageCode = translationRes.TargetLanguage,
739-
PluralForm = row.IsPlural ? PluralForms.Other : "",
765+
PluralForm = translationRes.PluralForm,
740766
Value = null,
741767
OriginalValue = null
742768
};
@@ -756,6 +782,44 @@ else
756782
}
757783
}
758784

785+
/// <summary>
786+
/// Builds a dictionary of source texts from the current UI state.
787+
/// This allows translating unsaved edits (what the user sees in the editor).
788+
/// For plural keys, includes all plural forms with "keyName:pluralForm" keys.
789+
/// </summary>
790+
private Dictionary<string, string> BuildSourceTextsFromRows(List<TranslationGridRow> rows, string defaultLanguage)
791+
{
792+
var sourceTexts = new Dictionary<string, string>();
793+
794+
foreach (var row in rows)
795+
{
796+
if (row.IsPlural)
797+
{
798+
// For plural keys, include all plural forms
799+
foreach (var pluralForm in PluralForms.All)
800+
{
801+
var cell = row.GetCell(defaultLanguage, pluralForm);
802+
if (cell != null && !string.IsNullOrEmpty(cell.Value))
803+
{
804+
// Use "keyName:pluralForm" format for plural keys
805+
sourceTexts[$"{row.KeyName}:{pluralForm}"] = cell.Value;
806+
}
807+
}
808+
}
809+
else
810+
{
811+
// For non-plural keys, just use the key name
812+
var cell = row.Translations.GetValueOrDefault(defaultLanguage);
813+
if (cell != null && !string.IsNullOrEmpty(cell.Value))
814+
{
815+
sourceTexts[row.KeyName] = cell.Value;
816+
}
817+
}
818+
}
819+
820+
return sourceTexts;
821+
}
822+
759823
private async Task DeleteKey(TranslationGridRow row)
760824
{
761825
_pendingDeleteRows = new[] { row };
@@ -767,14 +831,9 @@ else
767831

768832
private void OnKeyTypeChanged((TranslationGridRow Row, bool IsPlural) args)
769833
{
770-
// Track the edited row for the type change
771-
if (!_editedRows.ContainsKey(args.Row.KeyName))
772-
{
773-
_editedRows[args.Row.KeyName] = args.Row;
774-
}
775-
_hasUnsavedChanges = true;
776-
777-
// Force grid to re-render to update the plural indicator
834+
// Type change is on the clone - just update UI to show changes in the drawer.
835+
// The actual change will be applied when the user clicks "Apply" via SaveKeyDetails.
836+
// Don't track or mark as dirty here - that happens in SaveKeyDetails.
778837
StateHasChanged();
779838
}
780839

0 commit comments

Comments
 (0)