Bladeren bron

complete RU/EN language switcher across all GUI tabs

- app_window: replace hardcoded _TAB_NAMES with _TAB_NAV_KEYS + tr();
  add EN/RU toggle button pair (gold-outlined when active) in the
  nav bar right side; _on_language_change() calls i18n.set_language()
  then broadcasts retranslate_ui() to every tab; status bar also
  updates on switch
- scanning_tab: replace all hardcoded Russian ("Протоколы",
  "Геометрия", "Ориентация", "Поворот", "Матрица поворота",
  "Основные/Контраст/Разрешение/Система", "Нет данных",
  "Сканирование...", "Готово к сканированию", etc.) with tr() calls;
  fix "Run  Сканировать" / "Stop  Стоп" mixed-language typos;
  retranslate_ui() on both ProtocolListWidget and ScanningTab
- spectroscopy_tab: wire all toolbar buttons, group box titles, and
  BatchDialog strings through tr(); retranslate_ui() added
- Pulse sequence names (FID, SE, TSE), MRI plane labels (Axial/Cor/
  Sag), log messages, and file extensions stay English at all times

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
spacexerq 1 dag geleden
bovenliggende
commit
4a53a92b17
3 gewijzigde bestanden met toevoegingen van 158 en 61 verwijderingen
  1. 78 11
      apps/gui/src/app_window.py
  2. 46 23
      apps/gui/src/tabs/scanning_tab.py
  3. 34 27
      apps/gui/src/tabs/spectroscopy_tab.py

+ 78 - 11
apps/gui/src/app_window.py

@@ -10,6 +10,7 @@ from __future__ import annotations
 import os
 
 from PySide6.QtCore import Qt, QSize
+from src import i18n
 from PySide6.QtWidgets import (
     QApplication,
     QButtonGroup,
@@ -30,7 +31,13 @@ from src.tabs.scanning_tab import ScanningTab
 from src.tabs.seq_interp_tab import SeqInterpTab
 from src.tabs.spectroscopy_tab import SpectroscopyTab
 
-_TAB_NAMES = ["Sequence", "Scanner", "FID", "Scanning", "Spectroscopy"]
+_TAB_NAV_KEYS = [
+    "tab_nav_sequence",
+    "tab_nav_scanner",
+    "tab_nav_fid",
+    "tab_nav_scanning",
+    "tab_nav_spectro",
+]
 
 _NAV_BG = "#0f0f1e"
 _NAV_H = 38
@@ -53,6 +60,27 @@ QPushButton:hover:!checked {
     background: #17172e;
 }
 """
+_LANG_BTN_CSS = f"""
+QPushButton {{
+    background: transparent;
+    color: #555577;
+    border: 1px solid transparent;
+    border-radius: 3px;
+    padding: 0px 7px;
+    font-size: 11px;
+    font-weight: bold;
+    min-height: 22px;
+    max-height: 22px;
+}}
+QPushButton:checked {{
+    color: #f0c040;
+    border-color: #f0c040;
+}}
+QPushButton:hover:!checked {{
+    color: #aaaacc;
+}}
+"""
+
 _NAV_TOOLBAR_CSS = f"""
 QToolBar {{
     background: {_NAV_BG};
@@ -103,11 +131,11 @@ class LFMRIWindow(QMainWindow):
         self._tabs = QTabWidget()
         self._tabs.tabBar().hide()
         self._tabs.setDocumentMode(True)
-        self._tabs.addTab(self._seq_tab, _TAB_NAMES[0])
-        self._tabs.addTab(self._scanner_tab, _TAB_NAMES[1])
-        self._tabs.addTab(self._fid_tab, _TAB_NAMES[2])
-        self._tabs.addTab(self._scanning_tab, _TAB_NAMES[3])
-        self._tabs.addTab(self._spectroscopy_tab, _TAB_NAMES[4])
+        self._tabs.addTab(self._seq_tab,          i18n.tr(_TAB_NAV_KEYS[0]))
+        self._tabs.addTab(self._scanner_tab,       i18n.tr(_TAB_NAV_KEYS[1]))
+        self._tabs.addTab(self._fid_tab,           i18n.tr(_TAB_NAV_KEYS[2]))
+        self._tabs.addTab(self._scanning_tab,      i18n.tr(_TAB_NAV_KEYS[3]))
+        self._tabs.addTab(self._spectroscopy_tab,  i18n.tr(_TAB_NAV_KEYS[4]))
         self._tabs.currentChanged.connect(self._on_tab_changed)
         self.setCentralWidget(self._tabs)
 
@@ -145,8 +173,8 @@ class LFMRIWindow(QMainWindow):
         self._nav_btn_group.setExclusive(True)
         self._nav_tab_buttons: list[QPushButton] = []
 
-        for i, name in enumerate(_TAB_NAMES):
-            btn = QPushButton(name)
+        for i, key in enumerate(_TAB_NAV_KEYS):
+            btn = QPushButton(i18n.tr(key))
             btn.setCheckable(True)
             btn.setStyleSheet(_TAB_BTN_CSS)
             btn.setFixedHeight(_NAV_H)
@@ -163,6 +191,25 @@ class LFMRIWindow(QMainWindow):
         spacer.setStyleSheet(f"background: {_NAV_BG};")
         tb.addWidget(spacer)
 
+        # Language toggle (EN / RU)
+        self._lang_btn_group = QButtonGroup(self)
+        self._lang_btn_group.setExclusive(True)
+        self._btn_lang_en = QPushButton("EN")
+        self._btn_lang_en.setCheckable(True)
+        self._btn_lang_en.setChecked(True)
+        self._btn_lang_en.setStyleSheet(_LANG_BTN_CSS)
+        self._btn_lang_en.setCursor(Qt.PointingHandCursor)
+        self._btn_lang_ru = QPushButton("RU")
+        self._btn_lang_ru.setCheckable(True)
+        self._btn_lang_ru.setStyleSheet(_LANG_BTN_CSS)
+        self._btn_lang_ru.setCursor(Qt.PointingHandCursor)
+        self._lang_btn_group.addButton(self._btn_lang_en)
+        self._lang_btn_group.addButton(self._btn_lang_ru)
+        self._btn_lang_en.clicked.connect(lambda: self._on_language_change("en"))
+        self._btn_lang_ru.clicked.connect(lambda: self._on_language_change("ru"))
+        tb.addWidget(self._btn_lang_en)
+        tb.addWidget(self._btn_lang_ru)
+
     def _switch_tab(self, index: int) -> None:
         self._tabs.setCurrentIndex(index)
         self._nav_tab_buttons[index].setChecked(True)
@@ -173,7 +220,7 @@ class LFMRIWindow(QMainWindow):
             "QStatusBar { background: #0c0c1a; color: #555577; font-size: 11px; }"
         )
         self.setStatusBar(sb)
-        sb.showMessage(f"Active: {_TAB_NAMES[0]}")
+        sb.showMessage(f"{i18n.tr('active_tab')}: {i18n.tr(_TAB_NAV_KEYS[0])}")
 
     def _size_and_center(self) -> None:
         screen = QApplication.primaryScreen()
@@ -190,11 +237,31 @@ class LFMRIWindow(QMainWindow):
             self.resize(1440, 860)
 
     def _on_tab_changed(self, index: int) -> None:
-        name = _TAB_NAMES[index] if 0 <= index < len(_TAB_NAMES) else "-"
-        self.statusBar().showMessage(f"Active: {name}")
+        key = _TAB_NAV_KEYS[index] if 0 <= index < len(_TAB_NAV_KEYS) else "-"
+        self.statusBar().showMessage(f"{i18n.tr('active_tab')}: {i18n.tr(key)}")
         if 0 <= index < len(self._nav_tab_buttons):
             self._nav_tab_buttons[index].setChecked(True)
 
+    def _on_language_change(self, lang: str) -> None:
+        i18n.set_language(lang)
+        self.retranslate_ui()
+
+    def retranslate_ui(self) -> None:
+        for i, key in enumerate(_TAB_NAV_KEYS):
+            self._nav_tab_buttons[i].setText(i18n.tr(key))
+        cur = self._tabs.currentIndex()
+        key = _TAB_NAV_KEYS[cur] if 0 <= cur < len(_TAB_NAV_KEYS) else "-"
+        self.statusBar().showMessage(f"{i18n.tr('active_tab')}: {i18n.tr(key)}")
+        for tab in (
+            self._seq_tab,
+            self._scanner_tab,
+            self._fid_tab,
+            self._scanning_tab,
+            self._spectroscopy_tab,
+        ):
+            if hasattr(tab, "retranslate_ui"):
+                tab.retranslate_ui()
+
     def _on_fid_generated(self, path: str) -> None:
         self._seq_tab.load_seq_file(path)
         self._switch_tab(0)

+ 46 - 23
apps/gui/src/tabs/scanning_tab.py

@@ -32,6 +32,8 @@ try:
 except ImportError:
     _HAS_HTTPX = False
 
+from src import i18n
+
 # -- colour palette -------------------------------------------------------------
 _BG_DARK      = "#1a1a2e"
 _PANEL_BG     = "#2a2a2a"
@@ -323,11 +325,11 @@ class ProtocolListWidget(QWidget):
         lay.setContentsMargins(4, 8, 4, 4)
         lay.setSpacing(4)
 
-        header = QLabel("Протоколы")
-        header.setStyleSheet(
+        self._header_lbl = QLabel(i18n.tr("protocols_header"))
+        self._header_lbl.setStyleSheet(
             "color: #aaaaaa; font-weight: bold; font-size: 11px; background: transparent;"
         )
-        lay.addWidget(header)
+        lay.addWidget(self._header_lbl)
 
         self._list = QListWidget()
         self._list.setStyleSheet(f"""
@@ -356,6 +358,9 @@ class ProtocolListWidget(QWidget):
         self._list.currentTextChanged.connect(self.protocol_selected)
         lay.addWidget(self._list, stretch=1)
 
+    def retranslate_ui(self) -> None:
+        self._header_lbl.setText(i18n.tr("protocols_header"))
+
 
 # ==============================================================================
 class _ScanWorker(QThread):
@@ -465,6 +470,22 @@ class ScanningTab(QWidget):
     def set_orchestrator_url(self, url: str) -> None:
         self._orchestrator_url = url
 
+    def retranslate_ui(self) -> None:
+        self._protocol_list.retranslate_ui()
+        # param tabs: 0-3 = placeholders, 4 = geometry
+        _keys = ["tab_main", "tab_contrast", "tab_resolution", "tab_system", "tab_geometry"]
+        for idx, key in enumerate(_keys):
+            self._param_tabs.setTabText(idx, i18n.tr(key))
+        self._preset_group.setTitle(i18n.tr("grp_orientation"))
+        self._rot_group.setTitle(i18n.tr("grp_rotation"))
+        self._matrix_group.setTitle(i18n.tr("grp_rot_matrix"))
+        # scan button: keep text in sync with current scan state
+        if self._scan_timer.isActive():
+            self._btn_scan.setText(i18n.tr("btn_stop_scan"))
+        else:
+            self._btn_scan.setText(i18n.tr("btn_scan"))
+        self._update_scan_ready_state()
+
     # -- layout builders ----------------------------------------------------
 
     def _build_upper_area(self) -> QWidget:
@@ -525,15 +546,15 @@ class ScanningTab(QWidget):
             QTabBar::tab:hover:!selected { background: #222240; }
         """)
 
-        placeholder_tabs = ["Основные", "Контраст", "Разрешение", "Система"]
-        for name in placeholder_tabs:
-            w = QLabel(f"[ {name} - TODO ]")
+        _placeholder_keys = ["tab_main", "tab_contrast", "tab_resolution", "tab_system"]
+        for key in _placeholder_keys:
+            w = QLabel(f"[ {i18n.tr(key)} — TODO ]")
             w.setAlignment(Qt.AlignCenter)
             w.setStyleSheet("color: #555577; background: #16162a;")
-            self._param_tabs.addTab(w, name)
+            self._param_tabs.addTab(w, i18n.tr(key))
 
         geo_tab = self._build_geometry_tab()
-        self._param_tabs.addTab(geo_tab, "Геометрия")
+        self._param_tabs.addTab(geo_tab, i18n.tr("tab_geometry"))
         self._param_tabs.setCurrentWidget(geo_tab)
 
         outer.addWidget(self._param_tabs, stretch=1)
@@ -545,12 +566,12 @@ class ScanningTab(QWidget):
         action_lay.setContentsMargins(12, 6, 12, 6)
         action_lay.setSpacing(12)
 
-        self._status_label = QLabel("Нет данных")
+        self._status_label = QLabel(i18n.tr("no_data"))
         self._status_label.setStyleSheet("color: #666688; font-size: 11px;")
         action_lay.addWidget(self._status_label)
         action_lay.addStretch()
 
-        self._btn_scan = QPushButton("Run  Сканировать")
+        self._btn_scan = QPushButton(i18n.tr("btn_scan"))
         self._btn_scan.setCheckable(True)
         self._btn_scan.setMinimumWidth(140)
         self._btn_scan.setStyleSheet(
@@ -578,7 +599,8 @@ class ScanningTab(QWidget):
         lay.setSpacing(16)
 
         # -- orientation presets --------------------------------------------
-        preset_group = QGroupBox("Ориентация")
+        self._preset_group = QGroupBox(i18n.tr("grp_orientation"))
+        preset_group = self._preset_group
         preset_group.setStyleSheet(self._group_style())
         preset_lay = QVBoxLayout(preset_group)
         preset_lay.setSpacing(4)
@@ -598,7 +620,8 @@ class ScanningTab(QWidget):
         lay.addWidget(preset_group)
 
         # -- rotation angles ------------------------------------------------
-        rot_group = QGroupBox("Поворот")
+        self._rot_group = QGroupBox(i18n.tr("grp_rotation"))
+        rot_group = self._rot_group
         rot_group.setStyleSheet(self._group_style())
         form = QFormLayout(rot_group)
         form.setSpacing(6)
@@ -632,7 +655,8 @@ class ScanningTab(QWidget):
         lay.addWidget(rot_group)
 
         # -- rotation matrix display ----------------------------------------
-        matrix_group = QGroupBox("Матрица поворота")
+        self._matrix_group = QGroupBox(i18n.tr("grp_rot_matrix"))
+        matrix_group = self._matrix_group
         matrix_group.setStyleSheet(self._group_style())
         matrix_lay = QVBoxLayout(matrix_group)
         matrix_lay.setContentsMargins(8, 4, 8, 4)
@@ -766,9 +790,8 @@ class ScanningTab(QWidget):
         if checked:
             if self._seq_info is None:
                 QMessageBox.warning(
-                    self, "Нет данных",
-                    "Сначала загрузите и экспортируйте последовательность\n"
-                    'во вкладке "Sequence".'
+                    self, i18n.tr("dlg_no_data_title"),
+                    i18n.tr("dlg_no_seq_msg"),
                 )
                 self._btn_scan.setChecked(False)
                 return
@@ -780,34 +803,34 @@ class ScanningTab(QWidget):
             self._scan_worker.error.connect(self._on_scan_error)
             self._scan_worker.start()
             self._scan_timer.start()
-            self._btn_scan.setText("Stop  Стоп")
-            self._status_label.setText("Сканирование...")
+            self._btn_scan.setText(i18n.tr("btn_stop_scan"))
+            self._status_label.setText(i18n.tr("scanning_status"))
             self._status_label.setStyleSheet("color: #88ee88; font-size: 11px;")
             for v in self._viewers:
                 v.set_scanning(True)
         else:
             self._scan_timer.stop()
-            self._btn_scan.setText("Run  Сканировать")
+            self._btn_scan.setText(i18n.tr("btn_scan"))
             for v in self._viewers:
                 v.set_scanning(False)
             self._update_scan_ready_state()
 
     def _on_scan_done(self, msg: str) -> None:
-        self._status_label.setText(f"Готово ({msg})")
+        self._status_label.setText(f"{i18n.tr('done_status')} ({msg})")
         self._status_label.setStyleSheet("color: #66ccff; font-size: 11px;")
         self._btn_scan.setChecked(False)
 
     def _on_scan_error(self, err: str) -> None:
-        self._status_label.setText(f"Ошибка: {err[:60]}")
+        self._status_label.setText(f"{i18n.tr('error_prefix')}: {err[:60]}")
         self._status_label.setStyleSheet("color: #ee4444; font-size: 11px;")
         self._btn_scan.setChecked(False)
 
     def _update_scan_ready_state(self) -> None:
         if self._seq_info is not None:
-            self._status_label.setText("Готово к сканированию")
+            self._status_label.setText(i18n.tr("ready_to_scan"))
             self._status_label.setStyleSheet("color: #e65100; font-size: 11px;")
         else:
-            self._status_label.setText("Нет данных")
+            self._status_label.setText(i18n.tr("no_data"))
             self._status_label.setStyleSheet("color: #666688; font-size: 11px;")
 
     # -- protocol selection -------------------------------------------------

+ 34 - 27
apps/gui/src/tabs/spectroscopy_tab.py

@@ -38,6 +38,7 @@ from PySide6.QtWidgets import (
 from src.clients.spectroscopy_client import SpectroscopyClient, SpectroscopyError
 from src.gui.workers import OrchestratorWorker
 from src.gui.scheme_panel import system_is_dark
+from src import i18n
 
 
 # -- Colour palette -------------------------------------------------------------
@@ -160,13 +161,13 @@ class SpectroscopyTab(QWidget):
         lay.setContentsMargins(6, 4, 6, 4)
         lay.setSpacing(6)
 
-        self._btn_load = QPushButton("Load JSON...")
+        self._btn_load = QPushButton(i18n.tr("btn_load_json"))
         self._btn_load.setToolTip("Load a hardware JSON file and run NMR analysis")
         self._btn_load.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
         self._btn_load.clicked.connect(self._load_json)
         lay.addWidget(self._btn_load)
 
-        self._btn_analyze = QPushButton("Analyze")
+        self._btn_analyze = QPushButton(i18n.tr("btn_analyze"))
         self._btn_analyze.setToolTip(
             "Re-run analysis with the current parameters\n"
             "(same file, updated settings)"
@@ -178,7 +179,7 @@ class SpectroscopyTab(QWidget):
 
         lay.addWidget(_vsep())
 
-        self._btn_batch = QPushButton("Batch...")
+        self._btn_batch = QPushButton(i18n.tr("btn_batch"))
         self._btn_batch.setToolTip("Process a whole folder of hardware JSON files")
         self._btn_batch.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
         self._btn_batch.clicked.connect(self._open_batch_dialog)
@@ -186,14 +187,14 @@ class SpectroscopyTab(QWidget):
 
         lay.addWidget(_vsep())
 
-        self._btn_export = QPushButton("Export...")
+        self._btn_export = QPushButton(i18n.tr("btn_export_sp"))
         self._btn_export.setToolTip("Save analysis results (.mat / .csv / .npz)")
         self._btn_export.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
         self._btn_export.setEnabled(False)
         self._btn_export.clicked.connect(self._export_results)
         lay.addWidget(self._btn_export)
 
-        self._btn_clear = QPushButton("Clear")
+        self._btn_clear = QPushButton(i18n.tr("btn_clear"))
         self._btn_clear.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
         self._btn_clear.clicked.connect(self._clear_plots)
         lay.addWidget(self._btn_clear)
@@ -230,8 +231,8 @@ class SpectroscopyTab(QWidget):
         lay.setSpacing(8)
 
         # -- NMR Parameters -----------------------------------------------
-        nmr_grp = QGroupBox("NMR Parameters")
-        nmr_frm = QFormLayout(nmr_grp)
+        self._nmr_grp = QGroupBox(i18n.tr("grp_nmr_params"))
+        nmr_frm = QFormLayout(self._nmr_grp)
         nmr_frm.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow)
 
         self._sb_fc = QDoubleSpinBox()
@@ -293,11 +294,11 @@ class SpectroscopyTab(QWidget):
         nmr_frm.addRow("Avg num:",     self._sb_avg)
         nmr_frm.addRow("Data num:",    self._sb_dnum)
         nmr_frm.addRow("Channel:",     self._sb_ch)
-        lay.addWidget(nmr_grp)
+        lay.addWidget(self._nmr_grp)
 
         # -- Metadata ------------------------------------------------------
-        meta_grp = QGroupBox("Metadata")
-        meta_frm = QFormLayout(meta_grp)
+        self._meta_grp = QGroupBox(i18n.tr("grp_metadata"))
+        meta_frm = QFormLayout(self._meta_grp)
         meta_frm.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow)
         self._lbl_n   = QLabel("-")
         self._lbl_fs  = QLabel("-")
@@ -313,11 +314,11 @@ class SpectroscopyTab(QWidget):
         meta_frm.addRow("Avg range:", self._lbl_avg_range)
         meta_frm.addRow("Data range:", self._lbl_data_range)
         meta_frm.addRow("Channel range:", self._lbl_ch_range)
-        lay.addWidget(meta_grp)
+        lay.addWidget(self._meta_grp)
 
         # -- Metrics -------------------------------------------------------
-        met_grp = QGroupBox("Metrics")
-        met_frm = QFormLayout(met_grp)
+        self._met_grp = QGroupBox(i18n.tr("grp_metrics"))
+        met_frm = QFormLayout(self._met_grp)
         met_frm.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow)
         mono9 = QFont("Courier New", 9)
         self._lbl_peak_f   = QLabel("-"); self._lbl_peak_f.setFont(mono9)
@@ -326,7 +327,7 @@ class SpectroscopyTab(QWidget):
         met_frm.addRow("Peak freq:",  self._lbl_peak_f)
         met_frm.addRow("FWHM:",       self._lbl_fwhm)
         met_frm.addRow("Amplitude:",  self._lbl_peak_amp)
-        lay.addWidget(met_grp)
+        lay.addWidget(self._met_grp)
 
         lay.addStretch()
 
@@ -538,14 +539,22 @@ class SpectroscopyTab(QWidget):
         bar.setFixedHeight(22)
         lay = QHBoxLayout(bar)
         lay.setContentsMargins(6, 0, 6, 0)
-        self._status_lbl = QLabel(
-            "Click 'Load JSON...' to open a hardware JSON file and start NMR analysis"
-        )
+        self._status_lbl = QLabel(i18n.tr("spec_status_init"))
         self._status_lbl.setFont(QFont("Arial", 8))
         lay.addWidget(self._status_lbl)
         lay.addStretch()
         return bar
 
+    def retranslate_ui(self) -> None:
+        self._btn_load   .setText(i18n.tr("btn_load_json"))
+        self._btn_analyze.setText(i18n.tr("btn_analyze"))
+        self._btn_batch  .setText(i18n.tr("btn_batch"))
+        self._btn_export .setText(i18n.tr("btn_export_sp"))
+        self._btn_clear  .setText(i18n.tr("btn_clear"))
+        self._nmr_grp .setTitle(i18n.tr("grp_nmr_params"))
+        self._meta_grp.setTitle(i18n.tr("grp_metadata"))
+        self._met_grp .setTitle(i18n.tr("grp_metrics"))
+
     # -- Actions -----------------------------------------------------------
 
     def _load_json(self) -> None:
@@ -940,7 +949,7 @@ class BatchDialog(QDialog):
         parent: QWidget | None = None,
     ) -> None:
         super().__init__(parent)
-        self.setWindowTitle("Batch NMR Analysis")
+        self.setWindowTitle(i18n.tr("batch_title"))
         self.setMinimumSize(720, 540)
 
         self._client         = client
@@ -962,19 +971,17 @@ class BatchDialog(QDialog):
 
         # Folder picker
         fold_row = QHBoxLayout()
-        fold_row.addWidget(QLabel("Folder:"))
+        fold_row.addWidget(QLabel(i18n.tr("folder_label")))
         self._le_folder = QLineEdit()
         self._le_folder.setPlaceholderText("Path to folder containing *.json files")
         fold_row.addWidget(self._le_folder, stretch=1)
-        btn_browse = QPushButton("Browse...")
+        btn_browse = QPushButton(i18n.tr("btn_browse"))
         btn_browse.clicked.connect(self._browse_folder)
         fold_row.addWidget(btn_browse)
         lay.addLayout(fold_row)
 
         # Params toggle
-        self._cb_use_current = QCheckBox(
-            "Use current parameters from the Spectroscopy tab"
-        )
+        self._cb_use_current = QCheckBox(i18n.tr("cb_use_current"))
         self._cb_use_current.setChecked(True)
         self._cb_use_current.toggled.connect(self._on_use_current_toggled)
         lay.addWidget(self._cb_use_current)
@@ -985,7 +992,7 @@ class BatchDialog(QDialog):
 
         # Run row
         run_row = QHBoxLayout()
-        self._btn_run = QPushButton("Run Batch")
+        self._btn_run = QPushButton(i18n.tr("btn_run_batch"))
         self._btn_run.setFixedWidth(100)
         self._btn_run.clicked.connect(self._run_batch)
         run_row.addWidget(self._btn_run)
@@ -1013,18 +1020,18 @@ class BatchDialog(QDialog):
 
         # Bottom buttons
         bot_row = QHBoxLayout()
-        self._btn_csv = QPushButton("Export CSV...")
+        self._btn_csv = QPushButton(i18n.tr("btn_export_csv"))
         self._btn_csv.setEnabled(False)
         self._btn_csv.clicked.connect(self._export_csv)
         bot_row.addWidget(self._btn_csv)
         bot_row.addStretch()
-        btn_close = QPushButton("Close")
+        btn_close = QPushButton(i18n.tr("btn_close"))
         btn_close.clicked.connect(self.accept)
         bot_row.addWidget(btn_close)
         lay.addLayout(bot_row)
 
         # Status line
-        self._status_lbl = QLabel("Select a folder and click Run Batch.")
+        self._status_lbl = QLabel(i18n.tr("batch_status_init"))
         lay.addWidget(self._status_lbl)
 
     def _build_batch_params(self) -> QWidget: