feat: добавить локализацию TUI (en/ru) с переключением клавишей L

- Добавлены TuiResources (sealed record), Locale, LocalizationService
- Все строки интерфейса вынесены из .razor-файлов в TuiResources
- App.razor: клавиша L циклически переключает локаль, заголовок показывает [EN]/[RU]
- Дочерние компоненты получают Loc как параметр (stateless)
- Создан AGENT.tui.md с правилами работы с TUI для агентов
- Обновлены AGENTS.md и CLAUDE.md со ссылками на AGENT.tui.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-13 23:53:59 +03:00
parent 4819fbca6c
commit 01565b32d9
12 changed files with 279 additions and 91 deletions

View File

@@ -1,12 +1,12 @@
<Rows>
<Markup Content="Module Overview" Foreground="@UiPalette.Text" Decoration="@Spectre.Console.Decoration.Bold" />
<Markup Content="Up/Down select a module. Enter opens its settings." Foreground="@UiPalette.TextMuted" />
<Markup Content="@Loc.OverviewTitle" Foreground="@UiPalette.Text" Decoration="@Spectre.Console.Decoration.Bold" />
<Markup Content="@Loc.OverviewHint" Foreground="@UiPalette.TextMuted" />
<Markup Content=" " />
@if (Rows.Count == 0)
{
<Border BorderColor="@UiPalette.Frame" BoxBorder="@Spectre.Console.BoxBorder.Rounded" Padding="@(new Spectre.Console.Padding(0, 0, 0, 0))">
<Markup Content="No modules registered." Foreground="@UiPalette.TextDim" />
<Markup Content="@Loc.OverviewEmpty" Foreground="@UiPalette.TextDim" />
</Border>
}
else
@@ -30,6 +30,7 @@
[Parameter] public int SelectedIndex { get; set; }
[Parameter] public EventCallback<int> SelectedIndexChanged { get; set; }
[Parameter] public int ViewportRows { get; set; } = 3;
[Parameter] public TuiResources Loc { get; set; } = TuiResources.En;
private int[] GetOptions() => Enumerable.Range(0, Rows.Count).ToArray();
@@ -39,42 +40,27 @@
{
if (Rows.Count == 0)
{
return "No integration modules available.";
return Loc.OverviewEmpty;
}
var selected = Rows[Math.Clamp(SelectedIndex, 0, Rows.Count - 1)];
var state = selected.IsModuleEnabled ? "ON" : "OFF";
return $"{selected.ModuleName}: {selected.Description} | Module {state} | Tools {selected.ConfiguredTools}/{selected.TotalTools}";
var state = selected.IsModuleEnabled ? Loc.StateOn : Loc.StateOff;
return $"{selected.ModuleName}: {selected.Description} | {Loc.FooterModule} {state} | {Loc.FooterTools} {selected.ConfiguredTools}/{selected.TotalTools}";
}
private static Spectre.Console.Color GetRowForeground(OverviewRow row) =>
row.IsModuleEnabled ? UiPalette.Text : UiPalette.TextMuted;
private string FormatRow(int index)
{
var row = Rows[index];
var status = row.IsModuleEnabled ? "[ON] " : "[OFF]";
var status = row.IsModuleEnabled ? $"[{Loc.StateOn}] " : $"[{Loc.StateOff}]";
var text = $"{row.ModuleName,-12} {status} {row.ConfiguredTools,2}/{row.TotalTools,-2} {row.Description}";
return Fit(text, Math.Max(Console.WindowWidth - 12, 32));
}
private static string Fit(string text, int width)
{
if (width <= 0)
{
return string.Empty;
}
if (text.Length <= width)
{
return text.PadRight(width);
}
if (width <= 3)
{
return text[..width];
}
if (width <= 0) return string.Empty;
if (text.Length <= width) return text.PadRight(width);
if (width <= 3) return text[..width];
return text[..(width - 3)] + "...";
}
}