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

@@ -3,18 +3,19 @@
@inject ToolRegistryService Registry
@inject InMemoryLogSink LogSink
@inject GlobalKeyboardService KeyboardService
@inject LocalizationService Localization
@implements IDisposable
<Rows Expand="true">
<Panel Title="LazyBear MCP"
<Panel Title="@($"LazyBear MCP [{Localization.Label}]")"
TitleColor="@UiPalette.Accent"
BorderColor="@UiPalette.Frame"
Expand="true"
Height="@GetPanelHeight()"
Padding="@(new Spectre.Console.Padding(1, 0, 1, 0))">
<Rows Expand="true">
<Markup Content="Tab: switch tabs | Arrows: navigate | Space: toggle | Enter: open" Foreground="@UiPalette.TextMuted" />
<Markup Content="@Localization.Current.HintBar" Foreground="@UiPalette.TextMuted" />
<Markup Content=" " />
<Columns>
@@ -36,7 +37,8 @@
<OverviewTab Rows="@GetOverviewRows()"
SelectedIndex="@_overviewSelection"
SelectedIndexChanged="@OnOverviewSelectionChanged"
ViewportRows="@GetOverviewViewportRows()" />
ViewportRows="@GetOverviewViewportRows()"
Loc="@Localization.Current" />
}
else if (_activeTab == Tab.Logs)
{
@@ -45,14 +47,16 @@
SelectedIndexChanged="@OnLogSelectionChanged"
SelectedFilter="@_logFilters[_logFilterIndex]"
ViewportRows="@GetLogsViewportRows()"
IsStickyToBottom="@_logsStickToBottom" />
IsStickyToBottom="@_logsStickToBottom"
Loc="@Localization.Current" />
}
else
{
<SettingsTab Entries="@GetSettingsEntries()"
SelectedIndex="@_settingsSelection"
SelectedIndexChanged="@OnSettingsSelectionChanged"
ViewportRows="@GetSettingsViewportRows()" />
ViewportRows="@GetSettingsViewportRows()"
Loc="@Localization.Current" />
}
</Rows>
</Panel>
@@ -87,6 +91,7 @@
Registry.StateChanged += OnRegistryChanged;
LogSink.OnLog += OnNewLog;
KeyboardService.OnKeyPressed += OnConsoleKeyPressed;
Localization.OnChanged += OnLocaleChanged;
}
// Конвертация ConsoleKeyInfo → KeyboardEventArgs для переиспользования существующей логики
@@ -130,6 +135,9 @@
});
}
private void OnLocaleChanged() =>
InvokeAsync(StateHasChanged);
private void HandleKeyDown(KeyboardEventArgs args)
{
if (string.Equals(args.Key, "Tab", StringComparison.Ordinal))
@@ -138,6 +146,12 @@
return;
}
if (args.Key is "l" or "L")
{
Localization.SwitchNext();
return; // StateHasChanged вызовет OnLocaleChanged
}
switch (_activeTab)
{
case Tab.Overview:
@@ -447,7 +461,7 @@
toolName,
isModuleEnabled
? $"{module.ModuleName} / {toolName}"
: $"{module.ModuleName} / {toolName} (module is OFF, tool state is preserved)",
: $"{module.ModuleName} / {toolName} {Localization.Current.ModuleOffPreserved}",
isConfigured,
isModuleEnabled,
false,
@@ -527,11 +541,11 @@
private static bool IsInfoLevel(LogEntry entry) =>
entry.Level is LogLevel.Information or LogLevel.Debug or LogLevel.Trace;
private static string GetTabLabel(Tab tab) => tab switch
private string GetTabLabel(Tab tab) => tab switch
{
Tab.Overview => "Overview",
Tab.Logs => "Logs",
_ => "Settings"
Tab.Overview => Localization.Current.TabOverview,
Tab.Logs => Localization.Current.TabLogs,
_ => Localization.Current.TabSettings
};
public void Dispose()
@@ -539,5 +553,6 @@
Registry.StateChanged -= OnRegistryChanged;
LogSink.OnLog -= OnNewLog;
KeyboardService.OnKeyPressed -= OnConsoleKeyPressed;
Localization.OnChanged -= OnLocaleChanged;
}
}