Add translation support for TUI help groups and binding descriptions (#4363)

* Add translation support for TUI help groups and binding descriptions

Help group names shown via F1 (General, Navigation, Selection, Search) and key binding descriptions in the Textual footer (Down, Up, Cancel, Confirm, etc.) were hardcoded in English and never went through the translation system.

* ruff_format_check and mypy fixes

* Refactor _translate_bindings to accept BindingsMap instead of Any

Add TApp.translate_bindings() to avoid exporting private functions
across modules.

* Revert deprecated curses help.py change

* Move TApp import to module level in global_menu

* Change translate_bindings from staticmethod to member method
This commit is contained in:
Softer 2026-04-12 07:24:46 +03:00 committed by GitHub
parent d6de03ab76
commit 9a2d546882
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 275 additions and 2 deletions

View File

@ -28,6 +28,7 @@ from archinstall.lib.output import FormattedOutput
from archinstall.lib.packages.packages import list_available_packages, select_additional_packages
from archinstall.lib.pacman.config import PacmanConfig
from archinstall.lib.translationhandler import Language, tr, translation_handler
from archinstall.tui.ui.components import tui
from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup
@ -275,6 +276,8 @@ class GlobalMenu(AbstractMenu[None]):
if o.key is not None:
self._item_group.find_by_key(o.key).text = o.text
tui.translate_bindings()
async def _locale_selection(self, preset: LocaleConfiguration) -> LocaleConfiguration | None:
locale_config = await LocaleMenu(preset).show()
return locale_config

View File

@ -1709,6 +1709,123 @@ msgstr ""
msgid "Exit search mode"
msgstr ""
msgid "General"
msgstr ""
msgid "Navigation"
msgstr ""
msgid "Selection"
msgstr ""
msgid "Search"
msgstr ""
msgid "Down"
msgstr ""
msgid "Up"
msgstr ""
msgid "Confirm"
msgstr ""
msgid "Focus right"
msgstr ""
msgid "Focus left"
msgstr ""
msgid "Toggle"
msgstr ""
msgid "Show/Hide help"
msgstr ""
msgid "Quit"
msgstr ""
msgid "First"
msgstr ""
msgid "Last"
msgstr ""
msgid "Select"
msgstr ""
msgid "Page Up"
msgstr ""
msgid "Page Down"
msgstr ""
msgid "Page up"
msgstr ""
msgid "Page down"
msgstr ""
msgid "Page Left"
msgstr ""
msgid "Page Right"
msgstr ""
msgid "Cursor up"
msgstr ""
msgid "Cursor down"
msgstr ""
msgid "Cursor right"
msgstr ""
msgid "Cursor left"
msgstr ""
msgid "Top"
msgstr ""
msgid "Bottom"
msgstr ""
msgid "Home"
msgstr ""
msgid "End"
msgstr ""
msgid "Toggle option"
msgstr ""
msgid "Scroll Up"
msgstr ""
msgid "Scroll Down"
msgstr ""
msgid "Scroll Left"
msgstr ""
msgid "Scroll Right"
msgstr ""
msgid "Scroll Home"
msgstr ""
msgid "Scroll End"
msgstr ""
msgid "Focus Next"
msgstr ""
msgid "Focus Previous"
msgstr ""
msgid "Copy selected text"
msgstr ""
msgid ""
"labwc needs access to your seat (collection of hardware devices i.e. "
"keyboard, mouse, etc)"

View File

@ -1663,6 +1663,123 @@ msgstr "Запустити режим пошуку"
msgid "Exit search mode"
msgstr "Вийти з режиму пошуку"
msgid "General"
msgstr "Загальне"
msgid "Navigation"
msgstr "Навігація"
msgid "Selection"
msgstr "Вибір"
msgid "Search"
msgstr "Пошук"
msgid "Down"
msgstr "Вниз"
msgid "Up"
msgstr "Вгору"
msgid "Confirm"
msgstr "Підтвердити"
msgid "Focus right"
msgstr "Фокус вправо"
msgid "Focus left"
msgstr "Фокус вліво"
msgid "Toggle"
msgstr "Перемкнути"
msgid "Show/Hide help"
msgstr "Показати/Сховати довідку"
msgid "Quit"
msgstr "Вийти"
msgid "First"
msgstr "На початок"
msgid "Last"
msgstr "В кінець"
msgid "Select"
msgstr "Обрати"
msgid "Page Up"
msgstr "Сторінка вгору"
msgid "Page Down"
msgstr "Сторінка вниз"
msgid "Page up"
msgstr "Сторінка вгору"
msgid "Page down"
msgstr "Сторінка вниз"
msgid "Page Left"
msgstr "Сторінка вліво"
msgid "Page Right"
msgstr "Сторінка вправо"
msgid "Cursor up"
msgstr "Курсор вгору"
msgid "Cursor down"
msgstr "Курсор вниз"
msgid "Cursor right"
msgstr "Курсор вправо"
msgid "Cursor left"
msgstr "Курсор вліво"
msgid "Top"
msgstr "На початок"
msgid "Bottom"
msgstr "В кінець"
msgid "Home"
msgstr "Початок"
msgid "End"
msgstr "Кінець"
msgid "Toggle option"
msgstr "Перемкнути опцію"
msgid "Scroll Up"
msgstr "Прокрутка вгору"
msgid "Scroll Down"
msgstr "Прокрутка вниз"
msgid "Scroll Left"
msgstr "Прокрутка вліво"
msgid "Scroll Right"
msgstr "Прокрутка вправо"
msgid "Scroll Home"
msgstr "Прокрутка на початок"
msgid "Scroll End"
msgstr "Прокрутка в кінець"
msgid "Focus Next"
msgstr "Фокус на наступний"
msgid "Focus Previous"
msgstr "Фокус на попередній"
msgid "Copy selected text"
msgstr "Копіювати виділений текст"
msgid "labwc needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)"
msgstr "labwc потребує доступ до вашого місця (набору апаратних пристроїв, таких як клавіатура, миша тощо)"

View File

@ -1,13 +1,13 @@
import sys
from abc import ABC, abstractmethod
from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from dataclasses import dataclass, replace
from enum import Enum, auto
from typing import Any, ClassVar, Literal, TypeVar, cast, override
from textual import work
from textual.app import App, ComposeResult
from textual.binding import Binding
from textual.binding import Binding, BindingsMap
from textual.containers import Center, Horizontal, ScrollableContainer, Vertical
from textual.events import Key
from textual.geometry import Offset
@ -27,6 +27,18 @@ from archinstall.tui.ui.result import Result, ResultType
ValueT = TypeVar('ValueT')
def _translate_bindings(source: BindingsMap | None, target: BindingsMap) -> None:
"""Translate binding descriptions from source to target.
Uses source (original, immutable class-level cache) to avoid
double-translation on repeated calls (e.g. language switch).
"""
if source is None:
return
for key, bindings in source.key_to_bindings.items():
target.key_to_bindings[key] = [replace(b, description=tr(b.description)) if b.description else b for b in bindings]
class BaseScreen(Screen[Result[ValueT]]):
BINDINGS: ClassVar = [
Binding('escape', 'cancel_operation', 'Cancel', show=True),
@ -97,6 +109,7 @@ class LoadingScreen(BaseScreen[ValueT]):
yield Footer()
def on_mount(self) -> None:
_translate_bindings(self._merged_bindings, self._bindings)
if self._data_callback:
self._exec_callback()
else:
@ -129,6 +142,10 @@ class _OptionList(OptionList):
Binding('k', 'cursor_up', 'Up', show=False),
]
@override
def on_mount(self) -> None:
_translate_bindings(self._merged_bindings, self._bindings)
class OptionListScreen(BaseScreen[ValueT]):
"""
@ -271,6 +288,7 @@ class OptionListScreen(BaseScreen[ValueT]):
yield Footer()
def on_mount(self) -> None:
_translate_bindings(self._merged_bindings, self._bindings)
self._update_options(self._options)
self.query_one(OptionList).focus()
@ -356,6 +374,10 @@ class _SelectionList(SelectionList[ValueT]):
Binding('space', 'select', 'Toggle', show=True),
]
@override
def on_mount(self) -> None:
_translate_bindings(self._merged_bindings, self._bindings)
class SelectListScreen(BaseScreen[ValueT]):
"""
@ -500,6 +522,7 @@ class SelectListScreen(BaseScreen[ValueT]):
self._handle_search_action()
def on_mount(self) -> None:
_translate_bindings(self._merged_bindings, self._bindings)
self._update_options(self._options)
self.query_one(SelectionList).focus()
@ -670,6 +693,7 @@ class ConfirmationScreen(BaseScreen[ValueT]):
yield Footer()
def on_mount(self) -> None:
_translate_bindings(self._merged_bindings, self._bindings)
self._update_selection()
def action_focus_right(self) -> None:
@ -825,6 +849,7 @@ class InputScreen(BaseScreen[str]):
yield Footer()
def on_mount(self) -> None:
_translate_bindings(self._merged_bindings, self._bindings)
input_field = self.query_one('#main_input', Input)
input_field.focus()
@ -872,6 +897,10 @@ class _DataTable(DataTable[ValueT]):
Binding('enter', 'select_cursor', 'Confirm', show=True),
]
@override
def on_mount(self) -> None:
_translate_bindings(self._merged_bindings, self._bindings)
class TableSelectionScreen(BaseScreen[ValueT]):
BINDINGS: ClassVar = [
@ -992,6 +1021,7 @@ class TableSelectionScreen(BaseScreen[ValueT]):
yield Footer()
def on_mount(self) -> None:
_translate_bindings(self._merged_bindings, self._bindings)
self._display_header(True)
data_table = self.query_one(DataTable)
data_table.cell_padding = 2
@ -1245,6 +1275,7 @@ class _AppInstance(App[ValueT]):
_ = self.screen.mount(HelpPanel())
def on_mount(self) -> None:
_translate_bindings(self._merged_bindings, self._bindings)
self._run_worker()
@work
@ -1291,5 +1322,10 @@ class TApp:
assert TApp.app
TApp.app.exit(result)
def translate_bindings(self) -> None:
"""Re-translate app-level binding descriptions after language change."""
if TApp.app is not None:
_translate_bindings(TApp.app._merged_bindings, TApp.app._bindings)
tui = TApp()