Skip to content

Commit 2d21318

Browse files
committed
Add Source column to grid and improve TranslateDialog UX
- Add Source column to TranslationGrid showing SourceText for all rows - Add collapsible source text preview section in TranslateDialog - Replace language checkboxes with searchable multi-select dropdown - Add Select All/Clear All buttons for language selection
1 parent 3bb46f3 commit 2d21318

4 files changed

Lines changed: 146 additions & 23 deletions

File tree

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

Lines changed: 85 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -188,21 +188,70 @@
188188
</ChildContent>
189189
</RadzenFormField>
190190

191+
@* Source Text Preview *@
192+
@if (SourceTexts?.Count > 0)
193+
{
194+
<details style="margin-bottom: 0.5rem;">
195+
<summary style="cursor: pointer; margin-bottom: 0.5rem;">
196+
<RadzenText TextStyle="TextStyle.Subtitle2" TagName="TagName.Span">
197+
Source texts (@SourceTexts.Count)
198+
</RadzenText>
199+
</summary>
200+
<RadzenCard Style="max-height: 200px; overflow-y: auto; padding: 0.5rem;">
201+
<RadzenStack Gap="0.5rem">
202+
@foreach (var (key, text) in SourceTexts.Take(20))
203+
{
204+
<RadzenStack Gap="0.125rem">
205+
<RadzenText TextStyle="TextStyle.Caption" class="rz-color-secondary">@key</RadzenText>
206+
<RadzenText TextStyle="TextStyle.Body2">@TruncateText(text, 100)</RadzenText>
207+
</RadzenStack>
208+
}
209+
@if (SourceTexts.Count > 20)
210+
{
211+
<RadzenText TextStyle="TextStyle.Caption" class="rz-color-secondary">
212+
... and @(SourceTexts.Count - 20) more
213+
</RadzenText>
214+
}
215+
</RadzenStack>
216+
</RadzenCard>
217+
</details>
218+
}
219+
191220
<RadzenText TextStyle="TextStyle.Subtitle2">Target Languages</RadzenText>
192221
<RadzenStack Gap="0.5rem">
193-
@foreach (var lang in AvailableLanguages.Where(l => l != SourceLanguage))
194-
{
195-
var langCode = lang;
196-
var isSelected = _selectedLanguages.Contains(langCode);
197-
var displayName = GetLanguageDisplayName(langCode);
198-
var labelText = displayName != langCode.ToUpperInvariant()
199-
? $"{displayName} ({langCode.ToUpperInvariant()})"
200-
: langCode.ToUpperInvariant();
201-
<RadzenStack Orientation="Radzen.Orientation.Horizontal" AlignItems="Radzen.AlignItems.Center" Gap="0.5rem">
202-
<RadzenCheckBox TValue="bool" Value="@isSelected" Change="@(v => ToggleLanguage(langCode))" />
203-
<RadzenText TextStyle="TextStyle.Body2">@labelText</RadzenText>
204-
</RadzenStack>
205-
}
222+
@* Quick actions *@
223+
<RadzenStack Orientation="Radzen.Orientation.Horizontal" Gap="0.5rem" Wrap="FlexWrap.Wrap">
224+
<RadzenButton Text="Select All"
225+
Size="ButtonSize.ExtraSmall"
226+
Variant="Radzen.Variant.Outlined"
227+
Click="@SelectAllLanguages" />
228+
<RadzenButton Text="Clear All"
229+
Size="ButtonSize.ExtraSmall"
230+
Variant="Radzen.Variant.Outlined"
231+
Click="@ClearAllLanguages" />
232+
</RadzenStack>
233+
234+
@* Multi-select dropdown with chips *@
235+
<RadzenDropDown @bind-Value="@_selectedLanguagesList"
236+
Data="@_availableTargetLanguages"
237+
Multiple="true"
238+
AllowClear="true"
239+
AllowFiltering="true"
240+
FilterCaseSensitivity="FilterCaseSensitivity.CaseInsensitive"
241+
Placeholder="Select target languages..."
242+
Style="width: 100%;"
243+
Chips="true"
244+
MaxSelectedLabels="5">
245+
<Template Context="lang">
246+
@{
247+
var displayName = GetLanguageDisplayName(lang);
248+
var labelText = displayName != lang.ToUpperInvariant()
249+
? $"{displayName} ({lang.ToUpperInvariant()})"
250+
: lang.ToUpperInvariant();
251+
}
252+
@labelText
253+
</Template>
254+
</RadzenDropDown>
206255
</RadzenStack>
207256

208257
@if (!_selectedLanguages.Any())
@@ -379,7 +428,8 @@
379428
protected override async Task OnInitializedAsync()
380429
{
381430
_keysToTranslate = KeysToTranslate;
382-
_selectedLanguages = AvailableLanguages.Where(l => l != SourceLanguage).ToHashSet();
431+
// Default to no languages selected - user must explicitly choose
432+
_selectedLanguages = new HashSet<string>();
383433
await Task.WhenAll(LoadProvidersAsync(), CheckUsageLimitsAsync(), LoadGlossaryAsync());
384434
}
385435

@@ -435,10 +485,28 @@
435485
}
436486
}
437487

438-
private void ToggleLanguage(string lang)
488+
private IEnumerable<string> _availableTargetLanguages => AvailableLanguages.Where(l => l != SourceLanguage);
489+
490+
private IEnumerable<string> _selectedLanguagesList
491+
{
492+
get => _selectedLanguages;
493+
set => _selectedLanguages = new HashSet<string>(value ?? Enumerable.Empty<string>());
494+
}
495+
496+
private void SelectAllLanguages()
497+
{
498+
_selectedLanguages = new HashSet<string>(_availableTargetLanguages);
499+
}
500+
501+
private void ClearAllLanguages()
502+
{
503+
_selectedLanguages.Clear();
504+
}
505+
506+
private static string TruncateText(string? text, int maxLength)
439507
{
440-
if (_selectedLanguages.Contains(lang)) _selectedLanguages.Remove(lang);
441-
else _selectedLanguages.Add(lang);
508+
if (string.IsNullOrEmpty(text)) return "";
509+
return text.Length > maxLength ? text.Substring(0, maxLength) + "..." : text;
442510
}
443511

444512
private async Task CheckTmFirst()

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,27 @@
199199
</Template>
200200
</RadzenDataGridColumn>
201201

202+
@* Source text column *@
203+
<RadzenDataGridColumn TItem="TranslationGridRow" Title="Source" Width="300px" MinWidth="200px" Sortable="false" Resizable="true" Filterable="false">
204+
<Template Context="row">
205+
@{
206+
var sourceValue = row.IsPlural
207+
? (row.SourcePluralText ?? row.SourceText ?? "")
208+
: (row.SourceText ?? "");
209+
}
210+
@if (string.IsNullOrEmpty(sourceValue))
211+
{
212+
<RadzenText TextStyle="TextStyle.Body2" class="rz-color-secondary" Style="font-style: italic;">
213+
(no source)
214+
</RadzenText>
215+
}
216+
else
217+
{
218+
<RadzenText TextStyle="TextStyle.Body2">@sourceValue</RadzenText>
219+
}
220+
</Template>
221+
</RadzenDataGridColumn>
222+
202223
@* Language columns *@
203224
@foreach (var lang in VisibleLanguages)
204225
{

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ public class TranslationGridRow
3434
public string? KeyPath { get; set; }
3535
public bool IsPlural { get; set; }
3636
/// <summary>
37+
/// Source text for this key (value from default language file, msgid for PO format).
38+
/// Used to display the source text and for translation.
39+
/// </summary>
40+
public string? SourceText { get; set; }
41+
/// <summary>
3742
/// For plural keys, the source plural text pattern (PO msgid_plural or "other" form).
3843
/// Used to display the correct source text for translators.
3944
/// </summary>
@@ -156,6 +161,7 @@ public TranslationGridRow Clone()
156161
KeyName = KeyName,
157162
KeyPath = KeyPath,
158163
IsPlural = IsPlural,
164+
SourceText = SourceText,
159165
SourcePluralText = SourcePluralText,
160166
Comment = Comment,
161167
Version = Version,
@@ -192,6 +198,7 @@ public void ApplyFrom(TranslationGridRow source)
192198
{
193199
Comment = source.Comment;
194200
IsPlural = source.IsPlural;
201+
SourceText = source.SourceText;
195202
SourcePluralText = source.SourcePluralText;
196203
// Note: OriginalComment and OriginalIsPlural are preserved from this row,
197204
// not copied from source, so dirty detection works correctly

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

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,7 @@ else
260260
KeyName = detail.KeyName,
261261
KeyPath = detail.KeyPath,
262262
IsPlural = detail.IsPlural,
263+
SourceText = detail.SourceText,
263264
SourcePluralText = detail.SourcePluralText,
264265
Comment = detail.Comment,
265266
Version = detail.Version,
@@ -841,21 +842,47 @@ else
841842
{
842843
if (row.IsPlural)
843844
{
845+
// For plural keys, use SourcePluralText for "other" form, then fall back to cells
844846
foreach (var pluralForm in PluralForms.All)
845847
{
846-
var cell = row.GetCell(defaultLanguage, pluralForm);
847-
if (cell != null && !string.IsNullOrEmpty(cell.Value))
848+
string? sourceValue = null;
849+
850+
// Priority 1: Use SourcePluralText for "other" form (format-agnostic source)
851+
if (pluralForm == "other" && !string.IsNullOrEmpty(row.SourcePluralText))
852+
{
853+
sourceValue = row.SourcePluralText;
854+
}
855+
// Priority 2: Fall back to default language cell
856+
else
848857
{
849-
sourceTexts[$"{row.KeyName}:{pluralForm}"] = cell.Value;
858+
var cell = row.GetCell(defaultLanguage, pluralForm);
859+
if (cell != null && !string.IsNullOrEmpty(cell.Value))
860+
{
861+
sourceValue = cell.Value;
862+
}
863+
}
864+
865+
if (!string.IsNullOrEmpty(sourceValue))
866+
{
867+
sourceTexts[$"{row.KeyName}:{pluralForm}"] = sourceValue;
850868
}
851869
}
852870
}
853871
else
854872
{
855-
var cell = row.Translations.GetValueOrDefault(defaultLanguage);
856-
if (cell != null && !string.IsNullOrEmpty(cell.Value))
873+
// Priority 1: Use SourceText if available (format-agnostic source)
874+
if (!string.IsNullOrEmpty(row.SourceText))
875+
{
876+
sourceTexts[row.KeyName] = row.SourceText;
877+
}
878+
// Priority 2: Fall back to default language cell
879+
else
857880
{
858-
sourceTexts[row.KeyName] = cell.Value;
881+
var cell = row.Translations.GetValueOrDefault(defaultLanguage);
882+
if (cell != null && !string.IsNullOrEmpty(cell.Value))
883+
{
884+
sourceTexts[row.KeyName] = cell.Value;
885+
}
859886
}
860887
}
861888
}

0 commit comments

Comments
 (0)