spacexerq hace 2 semanas
padre
commit
a379b924e5
Se han modificado 1 ficheros con 125 adiciones y 0 borrados
  1. 125 0
      apps/gui/src/gui/tr_widgets.py

+ 125 - 0
apps/gui/src/gui/tr_widgets.py

@@ -0,0 +1,125 @@
+"""
+Reactive translation widgets for the LF-MRI GUI.
+
+Each widget registers itself with i18n as a listener and automatically
+re-translates when the language changes — no manual retranslate_ui needed.
+
+Usage
+-----
+    from src.gui.tr_widgets import TrLabel, TrPushButton, TrGroupBox
+    from src.gui.tr_widgets import bind_tab_text, bind_table_headers
+
+    # Instead of:
+    lbl = QLabel(i18n.tr("my_key"))          # + retranslate_ui: lbl.setText(i18n.tr(...))
+
+    # Write:
+    lbl = TrLabel("my_key")                  # auto-updates on language change
+
+    # QTabWidget tab texts:
+    bind_tab_text(tab_widget, idx=0, key="tab_main")
+
+    # QTableWidget column headers:
+    bind_table_headers(table, ["col_step", "col_status", "col_result"])
+"""
+from __future__ import annotations
+
+from PySide6.QtWidgets import (
+    QGroupBox, QLabel, QPushButton, QTabWidget, QTableWidget,
+)
+
+from src import i18n
+
+
+# ---------------------------------------------------------------------------
+# Base mixin
+# ---------------------------------------------------------------------------
+
+class _TrMixin:
+    """
+    Mixin that hooks a widget into i18n listeners.
+
+    Subclass must call ``_tr_init(key)`` at the end of its ``__init__``,
+    and implement ``_on_lang_changed()``.
+    """
+
+    def _tr_init(self, key: str) -> None:
+        self._tr_key = key
+        i18n.add_listener(self._on_lang_changed)
+        # QWidget.destroyed is a C++ signal — safe to connect in Python
+        self.destroyed.connect(self._tr_cleanup)  # type: ignore[attr-defined]
+
+    def _tr_cleanup(self) -> None:
+        i18n.remove_listener(self._on_lang_changed)
+
+    def _on_lang_changed(self) -> None:  # pragma: no cover
+        raise NotImplementedError
+
+
+# ---------------------------------------------------------------------------
+# Concrete reactive widgets
+# ---------------------------------------------------------------------------
+
+class TrLabel(QLabel, _TrMixin):
+    """QLabel whose text re-translates automatically on language change."""
+
+    def __init__(self, key: str, parent=None) -> None:
+        super().__init__(i18n.tr(key), parent)
+        self._tr_init(key)
+
+    def _on_lang_changed(self) -> None:
+        self.setText(i18n.tr(self._tr_key))
+
+
+class TrPushButton(QPushButton, _TrMixin):
+    """QPushButton whose text re-translates automatically on language change."""
+
+    def __init__(self, key: str, parent=None) -> None:
+        super().__init__(i18n.tr(key), parent)
+        self._tr_init(key)
+
+    def _on_lang_changed(self) -> None:
+        self.setText(i18n.tr(self._tr_key))
+
+
+class TrGroupBox(QGroupBox, _TrMixin):
+    """QGroupBox whose title re-translates automatically on language change."""
+
+    def __init__(self, key: str, parent=None) -> None:
+        super().__init__(i18n.tr(key), parent)
+        self._tr_init(key)
+
+    def _on_lang_changed(self) -> None:
+        self.setTitle(i18n.tr(self._tr_key))
+
+
+# ---------------------------------------------------------------------------
+# Helpers for non-widget items (tab texts, table headers)
+# ---------------------------------------------------------------------------
+
+def bind_tab_text(tab_widget: QTabWidget, idx: int, key: str) -> None:
+    """
+    Keep ``tab_widget.tabText(idx)`` translated at all times.
+
+    Call once after the tab is added::
+
+        self._tabs.addTab(w, i18n.tr("tab_main"))
+        bind_tab_text(self._tabs, 0, "tab_main")
+    """
+    def _update() -> None:
+        tab_widget.setTabText(idx, i18n.tr(key))
+
+    i18n.add_listener(_update)
+    tab_widget.destroyed.connect(lambda: i18n.remove_listener(_update))
+
+
+def bind_table_headers(table: QTableWidget, keys: list[str]) -> None:
+    """
+    Keep horizontal header labels of *table* translated at all times::
+
+        bind_table_headers(self._steps_table, ["col_step", "col_status", "col_result"])
+    """
+    def _update() -> None:
+        table.setHorizontalHeaderLabels([i18n.tr(k) for k in keys])
+
+    i18n.add_listener(_update)
+    table.destroyed.connect(lambda: i18n.remove_listener(_update))