Skip to content

Commit 68b706d

Browse files
Add FancyTable localization API
1 parent ba8715d commit 68b706d

8 files changed

Lines changed: 331 additions & 42 deletions

‎Components/FancyTable.razor‎

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
@typeparam TItem
22
@implements IDisposable
3+
@using System.Globalization
34

45
@{
56
PagedItemsResult pagedItems = GetPagedItems();
@@ -16,11 +17,11 @@
1617
else
1718
{
1819
<label class="d-inline-flex align-items-center gap-2 small text-muted">
19-
<span>Rows per page</span>
20+
<span>@ResolvedRowsPerPageLabel</span>
2021
<select class="form-select form-select-sm w-auto" value="@PaginationPageSize" @onchange="OnPageSizeChanged">
2122
@foreach (int option in EffectivePageSizeOptions)
2223
{
23-
<option value="@option">@(option == 0 ? "All" : option.ToString())</option>
24+
<option value="@option">@(option == 0 ? ResolvedAllItemsLabel : option.ToString(CultureInfo.CurrentCulture))</option>
2425
}
2526
</select>
2627
</label>
@@ -37,7 +38,7 @@
3738
else
3839
{
3940
<div class="@GetToolbarContainerClass()">
40-
<input class="form-control form-control-md" placeholder="@SearchPlaceholder" value="@SearchText" @oninput="OnSearchInput" @onkeyup="HandleSearchKeyPress" />
41+
<input class="form-control form-control-md" placeholder="@ResolvedSearchPlaceholder" value="@SearchText" @oninput="OnSearchInput" @onkeyup="HandleSearchKeyPress" />
4142

4243
@if (string.IsNullOrWhiteSpace(SearchText))
4344
{
@@ -102,7 +103,7 @@
102103
@if (HiddenColumns.Count > 0)
103104
{
104105
<div class="mt-2 ps-2 py-1">
105-
<small class="text-muted me-2">Hidden columns:</small>
106+
<small class="text-muted me-2">@ResolvedHiddenColumnsLabel</small>
106107
@foreach (string key in HiddenColumns)
107108
{
108109
FancyColumn<TItem>? column = Columns.FirstOrDefault(c => c.Key == key);
@@ -138,12 +139,12 @@
138139

139140
@if (pagedItems.Context.PageCount > 1)
140141
{
141-
<div class="btn-group btn-group-sm" role="group" aria-label="Pagination">
142+
<div class="btn-group btn-group-sm" role="group" aria-label="@ResolvedPaginationAriaLabel">
142143
<button type="button"
143144
class="btn btn-outline-secondary"
144145
disabled="@(!pagedItems.Context.HasPreviousPage)"
145146
@onclick="pagedItems.Context.GoToPreviousPage">
146-
Previous
147+
@ResolvedPreviousPageLabel
147148
</button>
148149
@foreach (int pageNumber in GetVisiblePageNumbers(pagedItems.Context))
149150
{
@@ -157,7 +158,7 @@
157158
class="btn btn-outline-secondary"
158159
disabled="@(!pagedItems.Context.HasNextPage)"
159160
@onclick="pagedItems.Context.GoToNextPage">
160-
Next
161+
@ResolvedNextPageLabel
161162
</button>
162163
</div>
163164
}
@@ -175,7 +176,7 @@
175176
public required IReadOnlyList<FancyColumn<TItem>> Columns { get; set; } = Array.Empty<FancyColumn<TItem>>();
176177

177178
[Parameter]
178-
public string SearchPlaceholder { get; set; } = "Search";
179+
public FancyTableTexts? Texts { get; set; }
179180

180181
[Parameter]
181182
public RenderFragment? ToolbarTemplate { get; set; }
@@ -256,6 +257,15 @@
256257
private int paginationPageSize = 25;
257258
private bool paginationStateInitialized;
258259

260+
private FancyTableTexts ResolvedTexts => Texts ?? FancyTableTexts.Default;
261+
private string ResolvedSearchPlaceholder => ResolvedTexts.SearchPlaceholder;
262+
private string ResolvedRowsPerPageLabel => ResolvedTexts.RowsPerPageLabel;
263+
private string ResolvedAllItemsLabel => ResolvedTexts.AllItemsLabel;
264+
private string ResolvedHiddenColumnsLabel => ResolvedTexts.HiddenColumnsLabel;
265+
private string ResolvedPaginationAriaLabel => ResolvedTexts.PaginationAriaLabel;
266+
private string ResolvedPreviousPageLabel => ResolvedTexts.PreviousPageLabel;
267+
private string ResolvedNextPageLabel => ResolvedTexts.NextPageLabel;
268+
259269
private IEnumerable<FancyColumn<TItem>> VisibleColumns =>
260270
Columns.Where(c => !HiddenColumns.Contains(c.Key));
261271

@@ -317,7 +327,7 @@
317327
private FancyPaginationContext<TItem> BuildPaginationContext(int totalItemCount, int filteredItemCount)
318328
{
319329
int pageCount = !IsPaginationActive || filteredItemCount == 0
320-
? (filteredItemCount == 0 ? 1 : 1)
330+
? 1
321331
: Math.Max(1, (int)Math.Ceiling(filteredItemCount / (double)PaginationPageSize));
322332

323333
int clampedPage = Math.Clamp(paginationCurrentPage, 1, pageCount);
@@ -361,7 +371,9 @@
361371
private void SortBy(string columnKey)
362372
{
363373
if (SortColumnKey == columnKey)
374+
{
364375
SortAscending = !SortAscending;
376+
}
365377
else
366378
{
367379
SortColumnKey = columnKey;
@@ -371,11 +383,16 @@
371383

372384
private string GetSortIcon(string key)
373385
{
374-
if (SortColumnKey != key) return "bi-arrow-down-up";
386+
if (SortColumnKey != key)
387+
{
388+
return "bi-arrow-down-up";
389+
}
390+
375391
return SortAscending ? "bi-sort-up" : "bi-sort-down";
376392
}
377393

378394
private void HideColumn(string key) => HiddenColumns.Add(key);
395+
379396
private void ShowColumn(string key) => HiddenColumns.Remove(key);
380397

381398
private void HandleSearchKeyPress(KeyboardEventArgs e)
@@ -481,12 +498,14 @@
481498
{
482499
if (context.FilteredItemCount == 0)
483500
{
484-
return context.TotalItemCount == 0 ? "No rows" : $"No matching rows ({context.TotalItemCount} total)";
501+
return context.TotalItemCount == 0
502+
? ResolvedTexts.NoRowsLabel
503+
: FormatResolvedText(ResolvedTexts.NoMatchingRowsWithTotalFormat, context.TotalItemCount);
485504
}
486505

487506
return context.FilteredItemCount == context.TotalItemCount
488-
? $"Showing {context.StartItemIndex}-{context.EndItemIndex} of {context.FilteredItemCount}"
489-
: $"Showing {context.StartItemIndex}-{context.EndItemIndex} of {context.FilteredItemCount} filtered rows ({context.TotalItemCount} total)";
507+
? FormatResolvedText(ResolvedTexts.ShowingItemsFormat, context.StartItemIndex, context.EndItemIndex, context.FilteredItemCount)
508+
: FormatResolvedText(ResolvedTexts.ShowingFilteredItemsFormat, context.StartItemIndex, context.EndItemIndex, context.FilteredItemCount, context.TotalItemCount);
490509
}
491510

492511
private IReadOnlyList<int> GetVisiblePageNumbers(FancyPaginationContext<TItem> context)
@@ -516,6 +535,9 @@
516535
private static string CombineClasses(string baseClass, string? additionalClass) =>
517536
string.IsNullOrWhiteSpace(additionalClass) ? baseClass : $"{baseClass} {additionalClass}";
518537

538+
private static string FormatResolvedText(string format, params object[] args) =>
539+
string.Format(CultureInfo.CurrentCulture, format, args);
540+
519541
private sealed class PagedItemsResult
520542
{
521543
public required IReadOnlyList<TItem> Items { get; init; }

‎Components/FancyTableTexts.cs‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
public sealed record FancyTableTexts
2+
{
3+
public static FancyTableTexts Default { get; } = new();
4+
5+
public string SearchPlaceholder { get; init; } = "Search";
6+
public string RowsPerPageLabel { get; init; } = "Rows per page";
7+
public string AllItemsLabel { get; init; } = "All";
8+
public string HiddenColumnsLabel { get; init; } = "Hidden columns:";
9+
public string PaginationAriaLabel { get; init; } = "Pagination";
10+
public string PreviousPageLabel { get; init; } = "Previous";
11+
public string NextPageLabel { get; init; } = "Next";
12+
public string NoRowsLabel { get; init; } = "No rows";
13+
public string NoMatchingRowsFormat { get; init; } = "No matching rows";
14+
public string NoMatchingRowsWithTotalFormat { get; init; } = "No matching rows ({0} total)";
15+
public string ShowingItemsFormat { get; init; } = "Showing {0}-{1} of {2}";
16+
public string ShowingFilteredItemsFormat { get; init; } = "Showing {0}-{1} of {2} filtered rows ({3} total)";
17+
public string ExpandLabel { get; init; } = "Expand";
18+
public string CollapseLabel { get; init; } = "Collapse";
19+
public string NoMatchingRootItemsFormat { get; init; } = "No matching rows ({0} total root items)";
20+
public string ShowingVisibleRowsFormat { get; init; } = "Showing {0}-{1} of {2} visible rows";
21+
}

‎Components/FancyTreeTable.razor‎

Lines changed: 44 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
@typeparam TItem where TItem : notnull
22
@implements IDisposable
3+
@using System.Globalization
34

45
@{
56
PagedNodesResult pagedNodes = GetPagedNodes();
@@ -16,11 +17,11 @@
1617
else
1718
{
1819
<label class="d-inline-flex align-items-center gap-2 small text-muted">
19-
<span>Rows per page</span>
20+
<span>@ResolvedRowsPerPageLabel</span>
2021
<select class="form-select form-select-sm w-auto" value="@PaginationPageSize" @onchange="OnPageSizeChanged">
2122
@foreach (int option in EffectivePageSizeOptions)
2223
{
23-
<option value="@option">@(option == 0 ? "All" : option.ToString())</option>
24+
<option value="@option">@(option == 0 ? ResolvedAllItemsLabel : option.ToString(CultureInfo.CurrentCulture))</option>
2425
}
2526
</select>
2627
</label>
@@ -37,7 +38,7 @@
3738
else
3839
{
3940
<div class="@GetToolbarContainerClass()">
40-
<input class="form-control form-control-md" placeholder="@SearchPlaceholder" value="@SearchText" @oninput="OnSearchInput" @onkeyup="HandleSearchKeyPress" />
41+
<input class="form-control form-control-md" placeholder="@ResolvedSearchPlaceholder" value="@SearchText" @oninput="OnSearchInput" @onkeyup="HandleSearchKeyPress" />
4142

4243
@if (string.IsNullOrWhiteSpace(SearchText))
4344
{
@@ -102,8 +103,8 @@
102103
RowLeadingContentTemplate="RowLeadingContentTemplate"
103104
RowTemplate="RowTemplate"
104105
RowTemplateSelector="RowTemplateSelector"
105-
ExpandLabel="ExpandLabel"
106-
CollapseLabel="CollapseLabel" />
106+
ExpandLabel="ResolvedExpandLabel"
107+
CollapseLabel="ResolvedCollapseLabel" />
107108
}
108109
</tbody>
109110
</table>
@@ -112,7 +113,7 @@
112113
@if (HiddenColumns.Count > 0)
113114
{
114115
<div class="mt-2 ps-2 py-1">
115-
<small class="text-muted me-2">Hidden columns:</small>
116+
<small class="text-muted me-2">@ResolvedHiddenColumnsLabel</small>
116117
@foreach (string key in HiddenColumns)
117118
{
118119
FancyColumn<TItem>? column = Columns.FirstOrDefault(c => c.Key == key);
@@ -148,12 +149,12 @@
148149

149150
@if (pagedNodes.Context.PageCount > 1)
150151
{
151-
<div class="btn-group btn-group-sm" role="group" aria-label="Pagination">
152+
<div class="btn-group btn-group-sm" role="group" aria-label="@ResolvedPaginationAriaLabel">
152153
<button type="button"
153154
class="btn btn-outline-secondary"
154155
disabled="@(!pagedNodes.Context.HasPreviousPage)"
155156
@onclick="pagedNodes.Context.GoToPreviousPage">
156-
Previous
157+
@ResolvedPreviousPageLabel
157158
</button>
158159
@foreach (int pageNumber in GetVisiblePageNumbers(pagedNodes.Context))
159160
{
@@ -167,7 +168,7 @@
167168
class="btn btn-outline-secondary"
168169
disabled="@(!pagedNodes.Context.HasNextPage)"
169170
@onclick="pagedNodes.Context.GoToNextPage">
170-
Next
171+
@ResolvedNextPageLabel
171172
</button>
172173
</div>
173174
}
@@ -191,13 +192,7 @@
191192
public Func<TItem, bool>? HasChildrenSelector { get; set; }
192193

193194
[Parameter]
194-
public string SearchPlaceholder { get; set; } = "Search";
195-
196-
[Parameter]
197-
public string ExpandLabel { get; set; } = "Expand";
198-
199-
[Parameter]
200-
public string CollapseLabel { get; set; } = "Collapse";
195+
public FancyTableTexts? Texts { get; set; }
201196

202197
[Parameter]
203198
public RenderFragment? ToolbarTemplate { get; set; }
@@ -286,6 +281,17 @@
286281
private int paginationPageSize = 25;
287282
private bool paginationStateInitialized;
288283

284+
private FancyTableTexts ResolvedTexts => Texts ?? FancyTableTexts.Default;
285+
private string ResolvedSearchPlaceholder => ResolvedTexts.SearchPlaceholder;
286+
private string ResolvedRowsPerPageLabel => ResolvedTexts.RowsPerPageLabel;
287+
private string ResolvedAllItemsLabel => ResolvedTexts.AllItemsLabel;
288+
private string ResolvedHiddenColumnsLabel => ResolvedTexts.HiddenColumnsLabel;
289+
private string ResolvedPaginationAriaLabel => ResolvedTexts.PaginationAriaLabel;
290+
private string ResolvedPreviousPageLabel => ResolvedTexts.PreviousPageLabel;
291+
private string ResolvedNextPageLabel => ResolvedTexts.NextPageLabel;
292+
private string ResolvedExpandLabel => ResolvedTexts.ExpandLabel;
293+
private string ResolvedCollapseLabel => ResolvedTexts.CollapseLabel;
294+
289295
private IEnumerable<FancyColumn<TItem>> VisibleColumns =>
290296
Columns.Where(c => !HiddenColumns.Contains(c.Key));
291297

@@ -432,10 +438,14 @@
432438
visibleNodes.Add(node);
433439

434440
if (!node.IsExpanded)
441+
{
435442
return;
443+
}
436444

437445
foreach (TreeNodeState<TItem> child in node.Children)
446+
{
438447
CollectExpandedNodes(child, visibleNodes);
448+
}
439449
}
440450

441451
private bool CollectSearchNodes(
@@ -456,7 +466,9 @@
456466

457467
includeNode = matchesSelf || childMatches.Count > 0;
458468
if (!includeNode)
469+
{
459470
return false;
471+
}
460472

461473
visibleNodes.Insert(insertIndex, node);
462474
visibleNodes.AddRange(childMatches);
@@ -466,7 +478,9 @@
466478
private void SortBy(string columnKey)
467479
{
468480
if (SortColumnKey == columnKey)
481+
{
469482
SortAscending = !SortAscending;
483+
}
470484
else
471485
{
472486
SortColumnKey = columnKey;
@@ -478,11 +492,16 @@
478492

479493
private string GetSortIcon(string key)
480494
{
481-
if (SortColumnKey != key) return "bi-arrow-down-up";
495+
if (SortColumnKey != key)
496+
{
497+
return "bi-arrow-down-up";
498+
}
499+
482500
return SortAscending ? "bi-sort-up" : "bi-sort-down";
483501
}
484502

485503
private void HideColumn(string key) => HiddenColumns.Add(key);
504+
486505
private void ShowColumn(string key) => HiddenColumns.Remove(key);
487506

488507
private Task ToggleNodeAsync(TreeNodeState<TItem> node)
@@ -742,12 +761,14 @@
742761
{
743762
if (context.FilteredItemCount == 0)
744763
{
745-
return context.TotalItemCount == 0 ? "No rows" : $"No matching rows ({context.TotalItemCount} total root items)";
764+
return context.TotalItemCount == 0
765+
? ResolvedTexts.NoRowsLabel
766+
: FormatResolvedText(ResolvedTexts.NoMatchingRootItemsFormat, context.TotalItemCount);
746767
}
747768

748769
return context.FilteredItemCount == context.TotalItemCount
749-
? $"Showing {context.StartItemIndex}-{context.EndItemIndex} of {context.FilteredItemCount}"
750-
: $"Showing {context.StartItemIndex}-{context.EndItemIndex} of {context.FilteredItemCount} visible rows";
770+
? FormatResolvedText(ResolvedTexts.ShowingItemsFormat, context.StartItemIndex, context.EndItemIndex, context.FilteredItemCount)
771+
: FormatResolvedText(ResolvedTexts.ShowingVisibleRowsFormat, context.StartItemIndex, context.EndItemIndex, context.FilteredItemCount);
751772
}
752773

753774
private IReadOnlyList<int> GetVisiblePageNumbers(FancyPaginationContext<TItem> context)
@@ -779,6 +800,9 @@
779800
private static string CombineClasses(string baseClass, string? additionalClass) =>
780801
string.IsNullOrWhiteSpace(additionalClass) ? baseClass : $"{baseClass} {additionalClass}";
781802

803+
private static string FormatResolvedText(string format, params object[] args) =>
804+
string.Format(CultureInfo.CurrentCulture, format, args);
805+
782806
private sealed class PagedNodesResult
783807
{
784808
public required IReadOnlyList<TreeNodeState<TItem>> Items { get; init; }
@@ -794,5 +818,3 @@
794818

795819
public void Dispose() => debounceTimer?.Dispose();
796820
}
797-
798-

0 commit comments

Comments
 (0)