|
|
@@ -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))
|