Skip to content

Commit 546450c

Browse files
committed
feat(bcasl): implement dynamic i18n for UI elements
- Apply translations to all UI widgets (buttons, labels, tooltips) - Add _update_ui_texts() method to refresh UI on language change - Store UI widgets as instance variables for dynamic updates - Use native_name in language selector instead of language codes - Add language_label translation key to all language files - Fix Browse button and Language label not translating All UI elements now respond immediately to language changes.
1 parent 2662029 commit 546450c

5 files changed

Lines changed: 133 additions & 64 deletions

File tree

bcasl/only_mod/app.py

Lines changed: 129 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,7 @@ def __init__(self, workspace_dir: Optional[str] = None):
411411
self._load_language_preference()
412412
self._load_theme_preference()
413413

414-
self.setWindowTitle("BCASL Standalone - Before Compilation Actions System Loader")
414+
self.setWindowTitle(self.language_manager.get("app_title", "BCASL Standalone"))
415415
self.setGeometry(100, 100, 1100, 800)
416416
self.setMinimumSize(900, 650)
417417

@@ -423,32 +423,32 @@ def __init__(self, workspace_dir: Optional[str] = None):
423423
layout.setSpacing(10)
424424

425425
# Workspace selection group
426-
ws_group = QGroupBox("Workspace Configuration")
427-
ws_layout = QHBoxLayout(ws_group)
428-
ws_label = QLabel("Workspace:")
429-
ws_label.setMinimumWidth(80)
430-
self.ws_display = QLabel(workspace_dir or "No workspace selected")
431-
self.ws_display.setToolTip("Currently selected workspace directory")
432-
ws_btn = QPushButton("Browse...")
433-
ws_btn.setMaximumWidth(100)
434-
ws_btn.clicked.connect(self._select_workspace)
435-
ws_layout.addWidget(ws_label)
426+
self.ws_group = QGroupBox(self.language_manager.get("workspace_config", "Workspace Configuration"))
427+
ws_layout = QHBoxLayout(self.ws_group)
428+
self.ws_label = QLabel(self.language_manager.get("workspace_label", "Workspace:"))
429+
self.ws_label.setMinimumWidth(80)
430+
self.ws_display = QLabel(workspace_dir or self.language_manager.get("no_workspace", "No workspace selected"))
431+
self.ws_display.setToolTip(self.language_manager.get("config_summary", "Currently selected workspace directory"))
432+
self.ws_browse_btn = QPushButton(self.language_manager.get("browse_button", "Browse..."))
433+
self.ws_browse_btn.setMaximumWidth(100)
434+
self.ws_browse_btn.clicked.connect(self._select_workspace)
435+
ws_layout.addWidget(self.ws_label)
436436
ws_layout.addWidget(self.ws_display)
437437
ws_layout.addStretch()
438-
ws_layout.addWidget(ws_btn)
439-
layout.addWidget(ws_group)
438+
ws_layout.addWidget(self.ws_browse_btn)
439+
layout.addWidget(self.ws_group)
440440

441441
# Config info
442-
self.config_info = QLabel("No configuration loaded")
443-
self.config_info.setToolTip("Configuration summary: enabled plugins, file patterns, etc.")
442+
self.config_info = QLabel(self.language_manager.get("no_config", "No configuration loaded"))
443+
self.config_info.setToolTip(self.language_manager.get("config_summary", "Configuration summary: enabled plugins, file patterns, etc."))
444444
layout.addWidget(self.config_info)
445445

446446
# Log output
447-
log_label = QLabel("Execution Log:")
447+
self.log_label = QLabel(self.language_manager.get("execution_log", "Execution Log:"))
448448
log_label_font = QFont()
449449
log_label_font.setBold(True)
450-
log_label.setFont(log_label_font)
451-
layout.addWidget(log_label)
450+
self.log_label.setFont(log_label_font)
451+
layout.addWidget(self.log_label)
452452
self.log = QTextEdit()
453453
self.log.setReadOnly(True)
454454
self.log.setMinimumHeight(280)
@@ -462,47 +462,60 @@ def __init__(self, workspace_dir: Optional[str] = None):
462462

463463
# Options and theme
464464
options_layout = QHBoxLayout()
465-
self.chk_async = QCheckBox("Run asynchronously")
465+
self.chk_async = QCheckBox(self.language_manager.get("run_async", "Run asynchronously"))
466466
self.chk_async.setChecked(True)
467-
self.chk_async.setToolTip("Execute BCASL in background thread for better responsiveness")
467+
self.chk_async.setToolTip(self.language_manager.get("run_async_tooltip", "Execute BCASL in background thread for better responsiveness"))
468468
options_layout.addWidget(self.chk_async)
469469

470470
# Language selector
471-
lang_label = QLabel("Language:")
471+
self.lang_label = QLabel(self.language_manager.get("language_label", "Language:"))
472472
self.lang_combo = QComboBox()
473-
self.lang_combo.addItems(list(self.language_manager.get_language_names()))
474-
self.lang_combo.setCurrentText(self.language_manager.current_language)
475-
self.lang_combo.currentTextChanged.connect(self._on_language_changed)
476-
self.lang_combo.setMaximumWidth(100)
473+
474+
# Store code -> display name mapping
475+
self._lang_codes = self.language_manager.get_language_names()
476+
self._lang_display_names = [
477+
self.language_manager.languages[code]['native_name']
478+
for code in self._lang_codes
479+
]
480+
481+
# Add display names to combo
482+
self.lang_combo.addItems(self._lang_display_names)
483+
484+
# Set current language by display name
485+
current_index = self._lang_codes.index(self.language_manager.current_language)
486+
self.lang_combo.setCurrentIndex(current_index)
487+
488+
self.lang_combo.currentIndexChanged.connect(self._on_language_changed)
489+
self.lang_combo.setMaximumWidth(150)
477490

478491
# Theme selector
479-
theme_label = QLabel("Theme:")
492+
self.theme_label = QLabel(self.language_manager.get("theme_label", "Theme:"))
480493
self.theme_combo = QComboBox()
481494
self.theme_combo.addItems(list(self.theme_manager.THEMES.keys()))
482495
self.theme_combo.setCurrentText(self.theme_manager.current_theme)
483496
self.theme_combo.currentTextChanged.connect(self._on_theme_changed)
484497
self.theme_combo.setMaximumWidth(120)
485498

486499
options_layout.addStretch()
487-
options_layout.addWidget(lang_label)
500+
options_layout.addWidget(self.lang_label)
488501
options_layout.addWidget(self.lang_combo)
489-
options_layout.addWidget(theme_label)
502+
options_layout.addWidget(self.theme_label)
490503
options_layout.addWidget(self.theme_combo)
491504
layout.addLayout(options_layout)
492505

493506
# Buttons
494507
btn_layout = QHBoxLayout()
495-
self.btn_config = QPushButton("⚙️ Configure Plugins")
496-
self.btn_config.setToolTip("Open plugin configuration dialog")
508+
self.btn_config = QPushButton(self.language_manager.get("configure_plugins", "⚙️ Configure Plugins"))
509+
self.btn_config.setToolTip(self.language_manager.get("configure_plugins_tooltip", "Open plugin configuration dialog"))
497510
self.btn_config.clicked.connect(self._open_config_dialog)
498-
self.btn_run = QPushButton("▶️ Run BCASL")
499-
self.btn_run.setToolTip("Execute BCASL pre-compilation actions")
511+
self.btn_run = QPushButton(self.language_manager.get("run_bcasl", "▶️ Run BCASL"))
512+
self.btn_run.setToolTip(self.language_manager.get("run_bcasl_tooltip", "Execute BCASL pre-compilation actions"))
500513
self.btn_run.clicked.connect(self._run_bcasl)
501-
self.btn_clear = QPushButton("🗑️ Clear Log")
502-
self.btn_clear.setToolTip("Clear the execution log")
514+
self.btn_clear = QPushButton(self.language_manager.get("clear_log", "🗑️ Clear Log"))
515+
self.btn_clear.setToolTip(self.language_manager.get("clear_log_tooltip", "Clear the execution log"))
503516
self.btn_clear.clicked.connect(self.log.clear)
504-
self.btn_exit = QPushButton("Exit")
505-
self.btn_exit.setToolTip("Close the application")
517+
self.btn_exit = QPushButton(self.language_manager.get("exit_button", "Exit"))
518+
self.btn_exit.setToolTip(self.language_manager.get("exit_tooltip", "Close the application"))
506519
self.btn_exit.clicked.connect(self.close)
507520

508521
btn_layout.addWidget(self.btn_config)
@@ -513,7 +526,7 @@ def __init__(self, workspace_dir: Optional[str] = None):
513526
layout.addLayout(btn_layout)
514527

515528
# Status bar
516-
self.statusBar().showMessage("Ready")
529+
self.statusBar().showMessage(self.language_manager.get("ready", "Ready"))
517530

518531
# Apply theme
519532
self._apply_theme()
@@ -566,12 +579,21 @@ def _save_theme_preference(self):
566579
except Exception:
567580
pass
568581

569-
def _on_language_changed(self, lang_code: str):
582+
def _on_language_changed(self, index: int):
570583
"""Handle language change."""
584+
if index < 0 or index >= len(self._lang_codes):
585+
return
586+
587+
lang_code = self._lang_codes[index]
571588
if self.language_manager.set_language(lang_code):
572589
self._save_language_preference()
573-
# Note: Full UI translation would require updating all text elements
574-
# For now, language preference is saved for next session
590+
self._update_ui_texts()
591+
592+
# Update language combo display names after language change
593+
self._lang_display_names = [
594+
self.language_manager.languages[code]['native_name']
595+
for code in self._lang_codes
596+
]
575597

576598
def _on_theme_changed(self, theme_name: str):
577599
"""Handle theme change."""
@@ -583,24 +605,59 @@ def _apply_theme(self):
583605
"""Apply current theme to the application."""
584606
stylesheet = self.theme_manager.get_stylesheet()
585607
self.setStyleSheet(stylesheet)
608+
609+
def _update_ui_texts(self):
610+
"""Update all UI text elements with current language."""
611+
self.setWindowTitle(self.language_manager.get("app_title", "BCASL Standalone"))
612+
self.ws_group.setTitle(self.language_manager.get("workspace_config", "Workspace Configuration"))
613+
self.ws_label.setText(self.language_manager.get("workspace_label", "Workspace:"))
614+
if not self.workspace_dir:
615+
self.ws_display.setText(self.language_manager.get("no_workspace", "No workspace selected"))
616+
self.ws_display.setToolTip(self.language_manager.get("config_summary", "Currently selected workspace directory"))
617+
618+
if self.config_info.text() == "No configuration loaded" or self.config_info.text().startswith("Aucune configuration"):
619+
self.config_info.setText(self.language_manager.get("no_config", "No configuration loaded"))
620+
self.config_info.setToolTip(self.language_manager.get("config_summary", "Configuration summary: enabled plugins, file patterns, etc."))
621+
622+
self.log_label.setText(self.language_manager.get("execution_log", "Execution Log:"))
623+
self.chk_async.setText(self.language_manager.get("run_async", "Run asynchronously"))
624+
self.chk_async.setToolTip(self.language_manager.get("run_async_tooltip", "Execute BCASL in background thread for better responsiveness"))
625+
626+
self.ws_browse_btn.setText(self.language_manager.get("browse_button", "Browse..."))
627+
self.lang_label.setText(self.language_manager.get("language_label", "Language:"))
628+
self.theme_label.setText(self.language_manager.get("theme_label", "Theme:"))
629+
630+
self.btn_config.setText(self.language_manager.get("configure_plugins", "⚙️ Configure Plugins"))
631+
self.btn_config.setToolTip(self.language_manager.get("configure_plugins_tooltip", "Open plugin configuration dialog"))
632+
self.btn_run.setText(self.language_manager.get("run_bcasl", "▶️ Run BCASL"))
633+
self.btn_run.setToolTip(self.language_manager.get("run_bcasl_tooltip", "Execute BCASL pre-compilation actions"))
634+
self.btn_clear.setText(self.language_manager.get("clear_log", "🗑️ Clear Log"))
635+
self.btn_clear.setToolTip(self.language_manager.get("clear_log_tooltip", "Clear the execution log"))
636+
self.btn_exit.setText(self.language_manager.get("exit_button", "Exit"))
637+
self.btn_exit.setToolTip(self.language_manager.get("exit_tooltip", "Close the application"))
638+
639+
# Update status bar if showing "Ready"
640+
status_text = self.statusBar().currentMessage()
641+
if status_text in ["Ready", "Prêt"]:
642+
self.statusBar().showMessage(self.language_manager.get("ready", "Ready"))
586643

587644
def _select_workspace(self):
588645
"""Select workspace directory."""
589646
folder = QFileDialog.getExistingDirectory(
590647
self,
591-
"Select Workspace Directory",
648+
self.language_manager.get("select_workspace", "Select Workspace Directory"),
592649
self.workspace_dir or os.path.expanduser("~"),
593650
)
594651
if folder:
595652
self.workspace_dir = folder
596653
self.ws_display.setText(folder)
597654
self._load_config_info()
598-
self.log.append(f"✅ Workspace selected: {folder}\n")
655+
self.log.append(self.language_manager.format("workspace_selected", path=folder) + "\n")
599656

600657
def _load_config_info(self):
601658
"""Load and display configuration info."""
602659
if not self.workspace_dir:
603-
self.config_info.setText("No workspace selected")
660+
self.config_info.setText(self.language_manager.get("no_workspace", "No workspace selected"))
604661
return
605662

606663
try:
@@ -617,44 +674,48 @@ def _load_config_info(self):
617674
exclude_patterns = cfg.get("exclude_patterns", [])
618675

619676
info = (
620-
f"Plugins: {enabled_count}/{total_count} enabled | "
621-
f"File patterns: {len(file_patterns)} | "
622-
f"Exclude patterns: {len(exclude_patterns)}"
677+
f"{self.language_manager.format('plugins_enabled', enabled=enabled_count, total=total_count)} | "
678+
f"{self.language_manager.format('file_patterns', count=len(file_patterns))} | "
679+
f"{self.language_manager.format('exclude_patterns', count=len(exclude_patterns))}"
623680
)
624681
self.config_info.setText(info)
625682
except Exception as e:
626-
self.config_info.setText(f"Error loading config: {e}")
683+
self.config_info.setText(self.language_manager.format("error_loading_config", error=str(e)))
627684

628685
def _open_config_dialog(self):
629686
"""Open plugin configuration dialog."""
630687
if not self.workspace_dir:
631688
QMessageBox.warning(
632689
self,
633-
"Warning",
634-
"Please select a workspace first.",
690+
self.language_manager.get("warning", "Warning"),
691+
self.language_manager.get("select_workspace_first", "Please select a workspace first."),
635692
)
636693
return
637694
try:
638695
open_bc_loader_dialog(self)
639696
self._load_config_info()
640697
except Exception as e:
641-
QMessageBox.critical(self, "Error", f"Failed to open config dialog: {e}")
698+
QMessageBox.critical(
699+
self,
700+
self.language_manager.get("error", "Error"),
701+
self.language_manager.format("config_dialog_error", error=str(e))
702+
)
642703

643704
def _run_bcasl(self):
644705
"""Run BCASL."""
645706
if not self.workspace_dir:
646707
QMessageBox.warning(
647708
self,
648-
"Warning",
649-
"Please select a workspace first.",
709+
self.language_manager.get("warning", "Warning"),
710+
self.language_manager.get("select_workspace_first", "Please select a workspace first."),
650711
)
651712
return
652713

653714
if self._is_running:
654715
QMessageBox.information(
655716
self,
656717
"Information",
657-
"BCASL is already running. Please wait for it to complete.",
718+
self.language_manager.get("bcasl_running", "BCASL is already running. Please wait for it to complete."),
658719
)
659720
return
660721

@@ -663,9 +724,9 @@ def _run_bcasl(self):
663724
self.btn_config.setEnabled(False)
664725
self.progress.setVisible(True)
665726
self.progress.setMaximum(0) # Indeterminate progress
666-
self.statusBar().showMessage("Running BCASL...")
727+
self.statusBar().showMessage(self.language_manager.get("running", "Running BCASL..."))
667728
self.log.append("\n" + "=" * 60)
668-
self.log.append("Starting BCASL execution...")
729+
self.log.append(self.language_manager.get("starting_execution", "Starting BCASL execution..."))
669730
self.log.append("=" * 60 + "\n")
670731

671732
def on_done(report):
@@ -676,25 +737,29 @@ def on_done(report):
676737
self.progress.setVisible(False)
677738

678739
if report is None:
679-
self.log.append("\n❌ BCASL execution failed or was cancelled.\n")
680-
self.statusBar().showMessage("Failed")
740+
self.log.append("\n" + self.language_manager.get("execution_failed", "❌ BCASL execution failed or was cancelled.") + "\n")
741+
self.statusBar().showMessage(self.language_manager.get("failed", "Failed"))
681742
else:
682743
try:
683744
self.log.append("\n" + "=" * 60)
684-
self.log.append("BCASL Execution Report:")
745+
self.log.append(self.language_manager.get("execution_report", "BCASL Execution Report:"))
685746
self.log.append("=" * 60 + "\n")
686747
for item in report:
687-
status = "✅ OK" if item.success else f"❌ FAIL: {item.error}"
748+
if item.success:
749+
status = self.language_manager.get("plugin_ok", "✅ OK")
750+
else:
751+
status = self.language_manager.format("plugin_fail", error=item.error)
688752
self.log.append(
689753
f" {item.plugin_id}: {status} ({item.duration_ms:.1f}ms)\n"
690754
)
691755
self.log.append("\n" + report.summary() + "\n")
692756
self.statusBar().showMessage(
693-
"Completed" if report.ok else "Completed with errors"
757+
self.language_manager.get("completed", "Completed") if report.ok
758+
else self.language_manager.get("completed_errors", "Completed with errors")
694759
)
695760
except Exception as e:
696-
self.log.append(f"\n⚠️ Error displaying report: {e}\n")
697-
self.statusBar().showMessage("Completed")
761+
self.log.append("\n" + self.language_manager.format("error_displaying_report", error=str(e)) + "\n")
762+
self.statusBar().showMessage(self.language_manager.get("completed", "Completed"))
698763

699764
try:
700765
if self.chk_async.isChecked():
@@ -703,12 +768,12 @@ def on_done(report):
703768
report = run_pre_compile(self)
704769
on_done(report)
705770
except Exception as e:
706-
self.log.append(f"\n❌ Error: {e}\n")
771+
self.log.append("\n" + self.language_manager.format("error_running_bcasl", error=str(e)) + "\n")
707772
self._is_running = False
708773
self.btn_run.setEnabled(True)
709774
self.btn_config.setEnabled(True)
710775
self.progress.setVisible(False)
711-
self.statusBar().showMessage("Error")
776+
self.statusBar().showMessage(self.language_manager.get("error", "Error"))
712777

713778
def closeEvent(self, event):
714779
"""Handle window close."""

bcasl/only_mod/languages/de.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"execution_log": "Ausführungsprotokoll:",
1313
"run_async": "Asynchron ausführen",
1414
"run_async_tooltip": "BCASL in einem Hintergrund-Thread ausführen für bessere Reaktionsfähigkeit",
15+
"language_label": "Sprache:",
1516
"theme_label": "Design:",
1617
"configure_plugins": "⚙️ Plugins Konfigurieren",
1718
"configure_plugins_tooltip": "Plugin-Konfigurationsdialog öffnen",

bcasl/only_mod/languages/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"execution_log": "Execution Log:",
1313
"run_async": "Run asynchronously",
1414
"run_async_tooltip": "Execute BCASL in background thread for better responsiveness",
15+
"language_label": "Language:",
1516
"theme_label": "Theme:",
1617
"configure_plugins": "⚙️ Configure Plugins",
1718
"configure_plugins_tooltip": "Open plugin configuration dialog",

bcasl/only_mod/languages/es.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"execution_log": "Registro de Ejecución:",
1313
"run_async": "Ejecutar de forma asincrónica",
1414
"run_async_tooltip": "Ejecutar BCASL en un hilo de fondo para mejor capacidad de respuesta",
15+
"language_label": "Idioma:",
1516
"theme_label": "Tema:",
1617
"configure_plugins": "⚙️ Configurar Complementos",
1718
"configure_plugins_tooltip": "Abrir el diálogo de configuración de complementos",

0 commit comments

Comments
 (0)