diff --git a/CHANGELOG.md b/CHANGELOG.md index 2aafd4d..cf10ee6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ Format basiert auf [Keep a Changelog](https://keepachangelog.com/de/1.1.0/). ## [Unreleased] +### Übersetzte Hilfetexte und gespeicherte Sprache (2026-10-01) +- 86 bislang fehlende Hilfetexte, Beschriftungen und Fehlermeldungen im Katalog ergänzt; Hilfetexte über die Übersetzungsfunktion angebunden (DE, EN, ES, ZH, JA, RU). +- Die Sprachauswahl erklärt in allen sechs Sprachen, dass Sprachänderungen beim nächsten Start übernommen werden. +- Sprachauswahl wird geladen und gespeichert; beim nächsten Start verwenden die angebundenen Texte und Qt-Standarddialoge die gespeicherte Sprache. +- Übersetzungsprüfung erkennt auch bereits angebundene `t(...)`-Aufrufe. Weitere fest deutsche Bedienelemente bleiben separat offen. + ### Sicheres Löschen - Die Löschbestätigung lässt sich über das Menü **Bearbeiten** oder die Einstellungen ein- und ausschalten. - Wenn die Bestätigung ausgeschaltet ist, verschiebt ExplorerPro ausgewählte Elemente in den System-Papierkorb. Schlägt das Verschieben fehl, bleibt das Element erhalten und ExplorerPro zeigt den Fehler an. diff --git a/locales/translations.json b/locales/translations.json index 5f3a860..27a5abe 100644 --- a/locales/translations.json +++ b/locales/translations.json @@ -2910,5 +2910,93 @@ "zh": "无法将项目移至回收站。", "ja": "項目をごみ箱に移動できませんでした。", "ru": "Не удалось переместить элемент в корзину." + }, + "Datei nicht gefunden": { + "de": "Datei nicht gefunden", + "en": "File not found", + "es": "Archivo no encontrado", + "zh": "找不到文件", + "ja": "ファイルが見つかりません", + "ru": "Файл не найден" + }, + "Dateien umbenannt, aber Fehler aufgetreten:": { + "de": "Dateien umbenannt, aber Fehler aufgetreten:", + "en": "Files renamed, but errors occurred:", + "es": "Archivos renombrados, pero se produjeron errores:", + "zh": "文件已重命名,但发生了错误:", + "ja": "ファイルの名前を変更しましたが、エラーが発生しました:", + "ru": "Файлы переименованы, но возникли ошибки:" + }, + "Dateien zurückgesetzt, Fehler:": { + "de": "Dateien zurückgesetzt, Fehler:", + "en": "Files restored; errors:", + "es": "Archivos restaurados; errores:", + "zh": "文件已还原;错误:", + "ja": "ファイルを復元しました。エラー:", + "ru": "Файлы восстановлены; ошибки:" + }, + "Pfad ist keine Datei": { + "de": "Pfad ist keine Datei", + "en": "The path is not a file", + "es": "La ruta no es un archivo", + "zh": "该路径不是文件", + "ja": "パスはファイルではありません", + "ru": "Путь не указывает на файл" + }, + "Ungültiger Pfad": { + "de": "Ungültiger Pfad", + "en": "Invalid path", + "es": "Ruta no válida", + "zh": "无效路径", + "ja": "無効なパス", + "ru": "Недопустимый путь" + }, + "Sprachänderungen werden beim nächsten Start übernommen.": { + "de": "Sprachänderungen werden beim nächsten Start übernommen.", + "en": "Language changes take effect on the next start.", + "es": "Los cambios de idioma se aplican en el próximo inicio.", + "zh": "语言更改将在下次启动时生效。", + "ja": "言語の変更は次回起動時に適用されます。", + "ru": "Изменение языка вступит в силу при следующем запуске." + }, + "Umbenennen": { + "de": "Umbenennen", + "en": "Rename", + "es": "Renombrar", + "zh": "重命名", + "ja": "名前を変更", + "ru": "Переименовать" + }, + "Status": { + "de": "Status", + "en": "Status", + "es": "Estado", + "zh": "状态", + "ja": "状態", + "ru": "Статус" + }, + "Erfolg": { + "de": "Erfolg", + "en": "Success", + "es": "Correcto", + "zh": "成功", + "ja": "成功", + "ru": "Успех" + }, + "Inhalt": { + "de": "Inhalt", + "en": "Content", + "es": "Contenido", + "zh": "内容", + "ja": "内容", + "ru": "Содержимое" + }, + "Diff kopieren": { + "de": "Diff kopieren", + "en": "Copy diff", + "es": "Copiar diferencias", + "zh": "复制差异", + "ja": "差分をコピー", + "ru": "Копировать различия" } } diff --git a/manage_translations.py b/manage_translations.py index 93847c9..49fe395 100644 --- a/manage_translations.py +++ b/manage_translations.py @@ -9,6 +9,7 @@ """ import argparse +import ast import json import os import re @@ -76,6 +77,20 @@ def find_german_strings(source_dir: str) -> Set[str]: for match in pattern.findall(content): if is_german(match): german_strings.add(match.strip()) + # Translated calls must remain visible to the auditor after + # setters are changed from setToolTip("...") to setToolTip(t("...")). + try: + tree = ast.parse(content) + except SyntaxError: + continue + for node in ast.walk(tree): + if (isinstance(node, ast.Call) + and isinstance(node.func, ast.Name) + and node.func.id == "t" and node.args + and isinstance(node.args[0], ast.Constant) + and isinstance(node.args[0].value, str) + and node.args[0].value.strip()): + german_strings.add(node.args[0].value.strip()) return german_strings @@ -123,7 +138,7 @@ def manage_translations(source_dir: str = ".", check_mode: bool = False) -> int: for lang, count in missing_by_lang.items(): print(f" - {lang}: {count} unvollständig") else: - print("\n[ok] Alle Strings haben vollständige Übersetzungen in allen 6 Sprachen (DE, EN, ES, ZH, JA, RU).") + print("\n[ok] Alle Katalogeinträge haben vollständige Übersetzungen in allen 6 Sprachen (DE, EN, ES, ZH, JA, RU).") print(f"\n[i] Gesamt: {len(translations)} Strings in {trans_file}") diff --git a/src/gui/batch_rename_dialog.py b/src/gui/batch_rename_dialog.py index 29597e5..6f6553d 100644 --- a/src/gui/batch_rename_dialog.py +++ b/src/gui/batch_rename_dialog.py @@ -87,7 +87,7 @@ def _setup_ui(self): self.search_edit = QLineEdit() self.search_edit.setAccessibleName("Suchbegriff für Umbenennung") self.search_edit.setAccessibleDescription("Zu suchender Text im ursprünglichen Dateinamen.") - self.search_edit.setToolTip("Zu ersetzender Teilstring im Dateinamen") + self.search_edit.setToolTip(t("Zu ersetzender Teilstring im Dateinamen")) search_lbl.setBuddy(self.search_edit) self.search_edit.textChanged.connect(self._update_preview) search_grid.addWidget(self.search_edit, 0, 1) @@ -97,7 +97,7 @@ def _setup_ui(self): self.replace_edit = QLineEdit() self.replace_edit.setAccessibleName("Ersetzungstext") self.replace_edit.setAccessibleDescription("Neuer Text, der anstelle des Suchbegriffs eingesetzt wird.") - self.replace_edit.setToolTip("Neuer Text für gefundene Treffer") + self.replace_edit.setToolTip(t("Neuer Text für gefundene Treffer")) replace_lbl.setBuddy(self.replace_edit) self.replace_edit.textChanged.connect(self._update_preview) search_grid.addWidget(self.replace_edit, 1, 1) @@ -105,7 +105,7 @@ def _setup_ui(self): self.regex_cb = QCheckBox(t("Reguläre Ausdrücke (Regex)")) self.regex_cb.setAccessibleName("Reguläre Ausdrücke") self.regex_cb.setAccessibleDescription("Aktiviert die Auswertung regulärer Ausdrücke im Suchbegriff.") - self.regex_cb.setToolTip("Suchbegriff als regulären Ausdruck auswerten") + self.regex_cb.setToolTip(t("Suchbegriff als regulären Ausdruck auswerten")) self.regex_cb.toggled.connect(self._update_preview) search_grid.addWidget(self.regex_cb, 2, 0, 1, 2) @@ -113,7 +113,7 @@ def _setup_ui(self): self.case_sensitive_cb.setChecked(True) self.case_sensitive_cb.setAccessibleName("Groß- und Kleinschreibung beachten") self.case_sensitive_cb.setAccessibleDescription("Unterscheidet zwischen Groß- und Kleinbuchstaben beim Suchen.") - self.case_sensitive_cb.setToolTip("Exakte Schreibweise beim Suchen berücksichtigen") + self.case_sensitive_cb.setToolTip(t("Exakte Schreibweise beim Suchen berücksichtigen")) self.case_sensitive_cb.toggled.connect(self._update_preview) search_grid.addWidget(self.case_sensitive_cb, 3, 0, 1, 2) @@ -128,7 +128,7 @@ def _setup_ui(self): self.prefix_edit = QLineEdit() self.prefix_edit.setAccessibleName("Dateinamen-Präfix") self.prefix_edit.setAccessibleDescription("Text, der vor jeden Dateinamen eingefügt wird.") - self.prefix_edit.setToolTip("Präfix vor den Dateinamen stellen") + self.prefix_edit.setToolTip(t("Präfix vor den Dateinamen stellen")) prefix_lbl.setBuddy(self.prefix_edit) self.prefix_edit.textChanged.connect(self._update_preview) affix_grid.addWidget(self.prefix_edit, 0, 1) @@ -138,7 +138,7 @@ def _setup_ui(self): self.suffix_edit = QLineEdit() self.suffix_edit.setAccessibleName("Dateinamen-Suffix") self.suffix_edit.setAccessibleDescription("Text, der an das Ende des Dateinamens vor die Dateiendung gehängt wird.") - self.suffix_edit.setToolTip("Suffix an den Dateinamen anhängen") + self.suffix_edit.setToolTip(t("Suffix an den Dateinamen anhängen")) suffix_lbl.setBuddy(self.suffix_edit) self.suffix_edit.textChanged.connect(self._update_preview) affix_grid.addWidget(self.suffix_edit, 1, 1) @@ -153,7 +153,7 @@ def _setup_ui(self): self.case_combo.addItem(t("Satzanfang groß"), "sentence") self.case_combo.setAccessibleName("Schreibweisenumwandlung") self.case_combo.setAccessibleDescription("Wandelt die Schreibweise aller Dateinamen einheitlich um.") - self.case_combo.setToolTip("Groß- und Kleinschreibung des Namens anpassen") + self.case_combo.setToolTip(t("Groß- und Kleinschreibung des Namens anpassen")) case_lbl.setBuddy(self.case_combo) self.case_combo.currentIndexChanged.connect(self._update_preview) affix_grid.addWidget(self.case_combo, 2, 1) @@ -178,7 +178,7 @@ def _setup_ui(self): self.num_enabled_cb = QCheckBox(t("Nummerierung aktivieren")) self.num_enabled_cb.setAccessibleName("Nummerierung aktivieren") self.num_enabled_cb.setAccessibleDescription("Schaltet die sequentielle Dateinummerierung ein.") - self.num_enabled_cb.setToolTip("Dateien fortlaufend nummerieren") + self.num_enabled_cb.setToolTip(t("Dateien fortlaufend nummerieren")) self.num_enabled_cb.toggled.connect(self._update_preview) num_grid.addWidget(self.num_enabled_cb, 0, 0, 1, 2) @@ -188,7 +188,7 @@ def _setup_ui(self): self.start_spin.setRange(0, 999999) self.start_spin.setValue(1) self.start_spin.setAccessibleName("Startwert der Nummerierung") - self.start_spin.setToolTip("Startnummer der Zählung") + self.start_spin.setToolTip(t("Startnummer der Zählung")) start_lbl.setBuddy(self.start_spin) self.start_spin.valueChanged.connect(self._update_preview) num_grid.addWidget(self.start_spin, 1, 1) @@ -199,7 +199,7 @@ def _setup_ui(self): self.step_spin.setRange(1, 1000) self.step_spin.setValue(1) self.step_spin.setAccessibleName("Schrittweite der Nummerierung") - self.step_spin.setToolTip("Inkrement pro Datei") + self.step_spin.setToolTip(t("Inkrement pro Datei")) step_lbl.setBuddy(self.step_spin) self.step_spin.valueChanged.connect(self._update_preview) num_grid.addWidget(self.step_spin, 2, 1) @@ -222,7 +222,7 @@ def _setup_ui(self): self.num_pos_combo.addItem(t("Am Anfang (Präfix)"), "prefix") self.num_pos_combo.addItem(t("Ersetzen"), "replace") self.num_pos_combo.setAccessibleName("Position der Nummerierung") - self.num_pos_combo.setToolTip("Position der Nummerierung im Dateinamen") + self.num_pos_combo.setToolTip(t("Position der Nummerierung im Dateinamen")) pos_lbl.setBuddy(self.num_pos_combo) self.num_pos_combo.currentIndexChanged.connect(self._update_preview) num_grid.addWidget(self.num_pos_combo, 4, 1) @@ -246,7 +246,7 @@ def _setup_ui(self): self.table.setAlternatingRowColors(True) self.table.setAccessibleName("Umbenennungs-Vorschautabelle") self.table.setAccessibleDescription("Zeigt Originalnamen, vorgeschlagene neue Namen und eventuelle Konflikte.") - self.table.setToolTip("Vorschautabelle aller zu ändernden Dateinamen") + self.table.setToolTip(t("Vorschautabelle aller zu ändernden Dateinamen")) preview_layout.addWidget(self.table) # Statusleiste / Zusammenfassung @@ -261,7 +261,7 @@ def _setup_ui(self): self.rollback_btn = QPushButton(t("Rückgängig")) self.rollback_btn.setAccessibleName("Letzte Umbenennung rückgängig machen") - self.rollback_btn.setToolTip("Macht die vorherige Umbenennungs-Aktion rückgängig") + self.rollback_btn.setToolTip(t("Macht die vorherige Umbenennungs-Aktion rückgängig")) self.rollback_btn.setEnabled(False) self.rollback_btn.clicked.connect(self._do_rollback) btn_layout.addWidget(self.rollback_btn) @@ -270,14 +270,14 @@ def _setup_ui(self): self.rename_btn = QPushButton(t("Umbenennen")) self.rename_btn.setAccessibleName("Dateien jetzt umbenennen") - self.rename_btn.setToolTip("Führt die geplante Umbenennung für alle Dateien aus (Enter)") + self.rename_btn.setToolTip(t("Führt die geplante Umbenennung für alle Dateien aus (Enter)")) self.rename_btn.setDefault(True) self.rename_btn.clicked.connect(self._do_rename) btn_layout.addWidget(self.rename_btn) self.close_btn = QPushButton(t("Schließen")) self.close_btn.setAccessibleName("Dialog schließen") - self.close_btn.setToolTip("Schließt das Mehrfachumbenennungs-Fenster (Esc)") + self.close_btn.setToolTip(t("Schließt das Mehrfachumbenennungs-Fenster (Esc)")) self.close_btn.setShortcut("Escape") self.close_btn.clicked.connect(self._on_close_clicked) btn_layout.addWidget(self.close_btn) diff --git a/src/gui/checksum_dialog.py b/src/gui/checksum_dialog.py index 7674b40..94c1254 100644 --- a/src/gui/checksum_dialog.py +++ b/src/gui/checksum_dialog.py @@ -14,6 +14,7 @@ from PySide6.QtGui import QFont from core.checksum_service import ChecksumWorker, verify_hash +from translator import t class ChecksumDialog(QDialog): @@ -108,7 +109,7 @@ def _setup_ui(self): self.copy_all_btn = QPushButton("📋 Alle Prüfsummen kopieren") self.copy_all_btn.setAccessibleName("Alle Prüfsummen kopieren") self.copy_all_btn.setAccessibleDescription("Kopiert alle berechneten Hash-Werte strukturiert in die Zwischenablage.") - self.copy_all_btn.setToolTip("Alle berechneten Prüfsummen (MD5, SHA-1, SHA-256, SHA-512) in die Zwischenablage kopieren") + self.copy_all_btn.setToolTip(t("Alle berechneten Prüfsummen (MD5, SHA-1, SHA-256, SHA-512) in die Zwischenablage kopieren")) self.copy_all_btn.clicked.connect(self._copy_all_hashes) self.copy_all_btn.setEnabled(False) hashes_layout.addRow("", self.copy_all_btn) @@ -128,7 +129,7 @@ def _setup_ui(self): self.verify_edit.setClearButtonEnabled(True) self.verify_edit.setAccessibleName("Erwartete Prüfsumme zur Verifikation") self.verify_edit.setAccessibleDescription("Eingabefeld für den Vergleichs-Hash. Erkennt automatisch den passenden Prüfsummen-Algorithmus.") - self.verify_edit.setToolTip("Geben Sie hier einen Hash ein, um die Integrität automatisch zu überprüfen") + self.verify_edit.setToolTip(t("Geben Sie hier einen Hash ein, um die Integrität automatisch zu überprüfen")) self.verify_edit.textChanged.connect(self._on_verify_text_changed) verify_layout.addWidget(self.verify_edit) @@ -145,7 +146,7 @@ def _setup_ui(self): self.close_btn = QPushButton("Schließen") self.close_btn.setAccessibleName("Dialog schließen") - self.close_btn.setToolTip("Schließt das Prüfsummen-Fenster (Esc)") + self.close_btn.setToolTip(t("Schließt das Prüfsummen-Fenster (Esc)")) self.close_btn.setShortcut("Escape") self.close_btn.setDefault(True) self.close_btn.clicked.connect(self.accept) @@ -161,7 +162,7 @@ def _start_calculation(self): return if not os.path.isfile(self.filepath): self.progress_bar.setVisible(False) - self.verify_result_label.setText("Fehler: Pfad ist ein Verzeichnis, keine Datei.") + self.verify_result_label.setText(t("Fehler: Pfad ist ein Verzeichnis, keine Datei.")) self.verify_result_label.setStyleSheet("color: red; font-weight: bold;") return diff --git a/src/gui/diff_dialog.py b/src/gui/diff_dialog.py index 6c88e16..f45e283 100644 --- a/src/gui/diff_dialog.py +++ b/src/gui/diff_dialog.py @@ -67,7 +67,7 @@ def _setup_ui(self): files_grid.addWidget(self.file1_edit, 0, 1) browse1_btn = QPushButton(t("Durchsuchen...")) browse1_btn.setAccessibleName("Datei 1 durchsuchen") - browse1_btn.setToolTip("Öffnet die Dateiauswahl für Datei 1 (Basis)") + browse1_btn.setToolTip(t("Öffnet die Dateiauswahl für Datei 1 (Basis)")) browse1_btn.clicked.connect(self._browse_file1) files_grid.addWidget(browse1_btn, 0, 2) @@ -81,7 +81,7 @@ def _setup_ui(self): files_grid.addWidget(self.file2_edit, 1, 1) browse2_btn = QPushButton(t("Durchsuchen...")) browse2_btn.setAccessibleName("Datei 2 durchsuchen") - browse2_btn.setToolTip("Öffnet die Dateiauswahl für Datei 2 (Vergleich)") + browse2_btn.setToolTip(t("Öffnet die Dateiauswahl für Datei 2 (Vergleich)")) browse2_btn.clicked.connect(self._browse_file2) files_grid.addWidget(browse2_btn, 1, 2) @@ -89,7 +89,7 @@ def _setup_ui(self): self.compare_btn.setStyleSheet("font-weight: bold; padding: 5px;") self.compare_btn.setAccessibleName("Dateien vergleichen") self.compare_btn.setAccessibleDescription("Startet die Differenzanalyse zwischen Datei 1 und Datei 2.") - self.compare_btn.setToolTip("Dateivergleich starten (Strg+Enter)") + self.compare_btn.setToolTip(t("Dateivergleich starten (Strg+Enter)")) self.compare_btn.setShortcut("Ctrl+Return") self.compare_btn.clicked.connect(self._do_compare) files_grid.addWidget(self.compare_btn, 2, 1, 1, 2) @@ -127,7 +127,7 @@ def _setup_ui(self): self.table.verticalHeader().setVisible(False) self.table.setAccessibleName("Diff-Ergebnistabelle") self.table.setAccessibleDescription("Zeilenweiser Vergleich beider Dateien: Grün = hinzugefügt, Rot = entfernt.") - self.table.setToolTip("Zeilenweiser Vergleich der beiden Dateien") + self.table.setToolTip(t("Zeilenweiser Vergleich der beiden Dateien")) main_layout.addWidget(self.table) # 4. Buttons @@ -144,7 +144,7 @@ def _setup_ui(self): self.close_btn = QPushButton(t("Schließen")) self.close_btn.setAccessibleName("Dialog schließen") - self.close_btn.setToolTip("Schließt den Vergleichs-Dialog (Esc)") + self.close_btn.setToolTip(t("Schließt den Vergleichs-Dialog (Esc)")) self.close_btn.setShortcut("Escape") self.close_btn.setDefault(True) self.close_btn.clicked.connect(self.accept) diff --git a/src/gui/main_window.py b/src/gui/main_window.py index 3bf6db8..789286c 100644 --- a/src/gui/main_window.py +++ b/src/gui/main_window.py @@ -180,12 +180,12 @@ def _setup_ui(self): self.case_sensitive_cb = QCheckBox("Groß-/Kleinschreibung beachten") self.case_sensitive_cb.setAccessibleName("Groß- und Kleinschreibung beachten") - self.case_sensitive_cb.setToolTip("Groß-/Kleinschreibung bei der Mustersuche berücksichtigen") + self.case_sensitive_cb.setToolTip(t("Groß-/Kleinschreibung bei der Mustersuche berücksichtigen")) options_layout.addWidget(self.case_sensitive_cb) self.whole_words_cb = QCheckBox("Nur ganze Wörter") self.whole_words_cb.setAccessibleName("Nur ganze Wörter") - self.whole_words_cb.setToolTip("Nur eigenständige Wörter als Treffer werten") + self.whole_words_cb.setToolTip(t("Nur eigenständige Wörter als Treffer werten")) options_layout.addWidget(self.whole_words_cb) self.auto_clear_cb = QCheckBox("Clipboard bei ROT automatisch leeren") @@ -214,11 +214,11 @@ def _setup_ui(self): ok_btn = buttons.button(QDialogButtonBox.StandardButton.Ok) if ok_btn: ok_btn.setAccessibleName("Einstellungen speichern") - ok_btn.setToolTip("Speichert die Datenschutz-Einstellungen (Enter)") + ok_btn.setToolTip(t("Speichert die Datenschutz-Einstellungen (Enter)")) cancel_btn = buttons.button(QDialogButtonBox.StandardButton.Cancel) if cancel_btn: cancel_btn.setAccessibleName("Abbrechen") - cancel_btn.setToolTip("Verwirft Änderungen (Esc)") + cancel_btn.setToolTip(t("Verwirft Änderungen (Esc)")) buttons.accepted.connect(self._save_and_close) buttons.rejected.connect(self.reject) layout.addWidget(buttons) diff --git a/src/gui/preview/preview_panel.py b/src/gui/preview/preview_panel.py index f0e760d..afcb030 100644 --- a/src/gui/preview/preview_panel.py +++ b/src/gui/preview/preview_panel.py @@ -17,6 +17,7 @@ from pathlib import Path from core.shortcut_utils import build_shortcut_preview_target, is_windows_shortcut +from translator import t # Optionale Imports try: @@ -347,7 +348,7 @@ def _setup_ui(self): self.tags_edit = QLineEdit() self.tags_edit.setPlaceholderText("Tags hinzufügen (kommagetrennt)") self.tags_edit.setAccessibleName("Metadaten-Tags") - self.tags_edit.setToolTip("Kommagetrennte Tags für die Datei eingeben") + self.tags_edit.setToolTip(t("Kommagetrennte Tags für die Datei eingeben")) self.tags_edit.editingFinished.connect(self.save_user_data) tags_layout.addWidget(self.tags_edit) @@ -361,7 +362,7 @@ def _setup_ui(self): self.notes_edit.setMaximumHeight(100) self.notes_edit.setPlaceholderText("Notizen zur Datei...") self.notes_edit.setAccessibleName("Datei-Notizen") - self.notes_edit.setToolTip("Freitextnotizen zur Datei eingeben") + self.notes_edit.setToolTip(t("Freitextnotizen zur Datei eingeben")) notes_layout.addWidget(self.notes_edit) layout.addWidget(notes_group) @@ -472,13 +473,13 @@ def _setup_ui(self): self.sheet_combo = QComboBox() self.sheet_combo.setMinimumWidth(120) self.sheet_combo.setAccessibleName("Excel-Arbeitsblatt") - self.sheet_combo.setToolTip("Arbeitsblatt der Excel-Arbeitsmappe auswählen") + self.sheet_combo.setToolTip(t("Arbeitsblatt der Excel-Arbeitsmappe auswählen")) self.sheet_combo.currentTextChanged.connect(self._on_sheet_changed) header.addWidget(self.sheet_combo, 1) self.open_extern_btn = QPushButton("Extern öffnen") self.open_extern_btn.setAccessibleName("In externer Anwendung öffnen") - self.open_extern_btn.setToolTip("Öffnet die Excel-Datei im Standardprogramm") + self.open_extern_btn.setToolTip(t("Öffnet die Excel-Datei im Standardprogramm")) self.open_extern_btn.setVisible(False) self.open_extern_btn.clicked.connect(self._open_extern) header.addWidget(self.open_extern_btn) diff --git a/src/gui/settings_dialog.py b/src/gui/settings_dialog.py index 4e4f4d7..65988fb 100644 --- a/src/gui/settings_dialog.py +++ b/src/gui/settings_dialog.py @@ -80,11 +80,11 @@ def _build_general_tab(self) -> QWidget: self.start_folder_edit.setPlaceholderText("Leer = Benutzerordner") self.start_folder_edit.setAccessibleName("Startordner") self.start_folder_edit.setAccessibleDescription("Standardordner, der beim Start der Anwendung geöffnet wird.") - self.start_folder_edit.setToolTip("Standardverzeichnis beim Programmstart") + self.start_folder_edit.setToolTip(t("Standardverzeichnis beim Programmstart")) path_row.addWidget(self.start_folder_edit) browse_btn = QPushButton("Auswählen...") browse_btn.setAccessibleName("Startordner auswählen") - browse_btn.setToolTip("Dateidialog zur Auswahl des Startordners öffnen") + browse_btn.setToolTip(t("Dateidialog zur Auswahl des Startordners öffnen")) browse_btn.clicked.connect(self._choose_start_folder) path_row.addWidget(browse_btn) form.addRow("Startordner:", path_row) @@ -92,14 +92,17 @@ def _build_general_tab(self) -> QWidget: self.language_cb = QComboBox() self.language_cb.setAccessibleName("Sprachauswahl") self.language_cb.setAccessibleDescription("Auswahl der Benutzeroberflächensprache.") - self.language_cb.setToolTip("Programmsprache für Menüs und Dialoge") + self.language_cb.setToolTip( + t("Programmsprache für Menüs und Dialoge") + "\n" + + t("Sprachänderungen werden beim nächsten Start übernommen.") + ) for code, display in TranslationSystem.get_language_display_names().items(): self.language_cb.addItem(display, code) form.addRow("Sprache:", self.language_cb) self.show_hidden_cb = QCheckBox("Versteckte Dateien anzeigen") self.show_hidden_cb.setAccessibleName("Versteckte Dateien anzeigen") - self.show_hidden_cb.setToolTip("Versteckte Dateien und Systemordner im Dateibrowser anzeigen") + self.show_hidden_cb.setToolTip(t("Versteckte Dateien und Systemordner im Dateibrowser anzeigen")) form.addRow(self.show_hidden_cb) self.confirm_delete_cb = QCheckBox(t("Vor dem Löschen nachfragen")) @@ -111,7 +114,7 @@ def _build_general_tab(self) -> QWidget: self.remember_size_cb = QCheckBox("Fenstergröße merken") self.remember_size_cb.setAccessibleName("Fenstergröße merken") - self.remember_size_cb.setToolTip("Fenstergröße und Position beim Beenden speichern") + self.remember_size_cb.setToolTip(t("Fenstergröße und Position beim Beenden speichern")) form.addRow(self.remember_size_cb) return page @@ -122,19 +125,19 @@ def _build_index_tab(self) -> QWidget: self.auto_index_cb = QCheckBox("Ordner automatisch indizieren") self.auto_index_cb.setAccessibleName("Ordner automatisch indizieren") - self.auto_index_cb.setToolTip("Verzeichnisse im Hintergrund automatisch für die Schnellsuche indizieren") + self.auto_index_cb.setToolTip(t("Verzeichnisse im Hintergrund automatisch für die Schnellsuche indizieren")) form.addRow(self.auto_index_cb) self.index_startup_cb = QCheckBox("Beim Programmstart indizieren") self.index_startup_cb.setAccessibleName("Beim Programmstart indizieren") - self.index_startup_cb.setToolTip("Indexprüfung direkt beim Start von ExplorerPro durchführen") + self.index_startup_cb.setToolTip(t("Indexprüfung direkt beim Start von ExplorerPro durchführen")) form.addRow(self.index_startup_cb) self.max_file_size_spin = QSpinBox() self.max_file_size_spin.setRange(1, 10000) self.max_file_size_spin.setSuffix(" MB") self.max_file_size_spin.setAccessibleName("Maximale Dateigröße für den Index") - self.max_file_size_spin.setToolTip("Dateien über dieser Größe werden nicht indiziert") + self.max_file_size_spin.setToolTip(t("Dateien über dieser Größe werden nicht indiziert")) form.addRow("Maximale Dateigröße:", self.max_file_size_spin) return page @@ -145,29 +148,29 @@ def _build_preview_tab(self) -> QWidget: self.show_preview_cb = QCheckBox("Vorschaufenster anzeigen") self.show_preview_cb.setAccessibleName("Vorschaufenster anzeigen") - self.show_preview_cb.setToolTip("Vorschauleiste auf der rechten Seite einblenden") + self.show_preview_cb.setToolTip(t("Vorschauleiste auf der rechten Seite einblenden")) form.addRow(self.show_preview_cb) self.preview_images_cb = QCheckBox("Bilder in der Vorschau anzeigen") self.preview_images_cb.setAccessibleName("Bilder in der Vorschau anzeigen") - self.preview_images_cb.setToolTip("Miniaturansichten für Grafikdateien rendern") + self.preview_images_cb.setToolTip(t("Miniaturansichten für Grafikdateien rendern")) form.addRow(self.preview_images_cb) self.preview_pdfs_cb = QCheckBox("PDF-Dateien in der Vorschau anzeigen") self.preview_pdfs_cb.setAccessibleName("PDF-Dateien in der Vorschau anzeigen") - self.preview_pdfs_cb.setToolTip("Erste Seite von PDF-Dokumenten im Vorschaufenster darstellen") + self.preview_pdfs_cb.setToolTip(t("Erste Seite von PDF-Dokumenten im Vorschaufenster darstellen")) form.addRow(self.preview_pdfs_cb) self.preview_code_cb = QCheckBox("Quelltext in der Vorschau anzeigen") self.preview_code_cb.setAccessibleName("Quelltext in der Vorschau anzeigen") - self.preview_code_cb.setToolTip("Syntaxhervorhebung für Quellcode und Textdateien nutzen") + self.preview_code_cb.setToolTip(t("Syntaxhervorhebung für Quellcode und Textdateien nutzen")) form.addRow(self.preview_code_cb) self.max_preview_spin = QSpinBox() self.max_preview_spin.setRange(1, 1000) self.max_preview_spin.setSuffix(" MB") self.max_preview_spin.setAccessibleName("Maximale Dateigröße für die Vorschau") - self.max_preview_spin.setToolTip("Maximale Dateigröße für generierte Vorschauen") + self.max_preview_spin.setToolTip(t("Maximale Dateigröße für generierte Vorschauen")) form.addRow("Maximale Vorschaugröße:", self.max_preview_spin) return page @@ -178,12 +181,12 @@ def _build_privacy_tab(self) -> QWidget: self.clipboard_monitor_cb = QCheckBox("Zwischenablage überwachen") self.clipboard_monitor_cb.setAccessibleName("Zwischenablage überwachen") - self.clipboard_monitor_cb.setToolTip("Zwischenablage kontinuierlich auf schutzwürdige Muster überwachen") + self.clipboard_monitor_cb.setToolTip(t("Zwischenablage kontinuierlich auf schutzwürdige Muster überwachen")) form.addRow(self.clipboard_monitor_cb) self.auto_block_cb = QCheckBox("Sensible Inhalte automatisch blockieren") self.auto_block_cb.setAccessibleName("Sensible Inhalte automatisch blockieren") - self.auto_block_cb.setToolTip("Kopieren hochsensibler Daten wie Passwörter und API-Keys blockieren") + self.auto_block_cb.setToolTip(t("Kopieren hochsensibler Daten wie Passwörter und API-Keys blockieren")) form.addRow(self.auto_block_cb) self.notifications_cb = QCheckBox("Hinweise anzeigen") @@ -206,7 +209,7 @@ def _build_appearance_tab(self) -> QWidget: self.theme_combo = QComboBox() self.theme_combo.setAccessibleName("Farbschema") - self.theme_combo.setToolTip("Farbschema der Benutzeroberfläche") + self.theme_combo.setToolTip(t("Farbschema der Benutzeroberfläche")) for value, label in self.THEMES: self.theme_combo.addItem(label, value) form.addRow("Farbschema:", self.theme_combo) @@ -214,14 +217,14 @@ def _build_appearance_tab(self) -> QWidget: self.font_size_spin = QSpinBox() self.font_size_spin.setRange(6, 32) self.font_size_spin.setAccessibleName("Schriftgröße") - self.font_size_spin.setToolTip("Basisschriftgröße in Punkten") + self.font_size_spin.setToolTip(t("Basisschriftgröße in Punkten")) form.addRow("Schriftgröße:", self.font_size_spin) self.icon_size_spin = QSpinBox() self.icon_size_spin.setRange(12, 64) self.icon_size_spin.setSuffix(" px") self.icon_size_spin.setAccessibleName("Symbolgröße") - self.icon_size_spin.setToolTip("Größe der Dateisymbole in Pixeln") + self.icon_size_spin.setToolTip(t("Größe der Dateisymbole in Pixeln")) form.addRow("Symbolgröße:", self.icon_size_spin) return page @@ -239,6 +242,9 @@ def _load_settings(self): """Überträgt die gespeicherten Werte in die Bedienelemente.""" get = self.settings.get + language_index = self.language_cb.findData(get("general", "language", "de")) + self.language_cb.setCurrentIndex(language_index if language_index >= 0 else 0) + self.start_folder_edit.setText(get("general", "start_folder", "") or "") self.show_hidden_cb.setChecked(bool(get("general", "show_hidden_files", False))) self.confirm_delete_cb.setChecked(get("general", "confirm_delete", True) is not False) @@ -270,6 +276,7 @@ def collect_settings(self) -> dict: """Liest die Bedienelemente aus und gibt die Werte als Dict zurück.""" return { "general": { + "language": self.language_cb.currentData(), "start_folder": self.start_folder_edit.text().strip(), "show_hidden_files": self.show_hidden_cb.isChecked(), "confirm_delete": self.confirm_delete_cb.isChecked(), diff --git a/src/gui/sidebar/advanced_search_dialog.py b/src/gui/sidebar/advanced_search_dialog.py index 5804e74..3eb20b9 100644 --- a/src/gui/sidebar/advanced_search_dialog.py +++ b/src/gui/sidebar/advanced_search_dialog.py @@ -13,6 +13,7 @@ ) from PySide6.QtCore import Qt, Signal, QDate, QThread, Slot from pathlib import Path +from translator import t class AdvancedSearchWorker(QThread): @@ -108,7 +109,7 @@ def _setup_ui(self): self.query_input.setPlaceholderText("Suchbegriff eingeben...") self.query_input.setAccessibleName("Suchbegriff") self.query_input.setAccessibleDescription("Suchbegriff für Dateiname, Inhalt oder Pfad.") - self.query_input.setToolTip("Suchbegriff eingeben (Enter startet die Suche)") + self.query_input.setToolTip(t("Suchbegriff eingeben (Enter startet die Suche)")) self.query_input.returnPressed.connect(self._do_search) search_layout.addWidget(self.query_input) @@ -129,16 +130,16 @@ def _setup_ui(self): self.search_name_cb = QCheckBox("Dateiname") self.search_name_cb.setChecked(True) self.search_name_cb.setAccessibleName("Im Dateinamen suchen") - self.search_name_cb.setToolTip("Dateinamen durchsuchen") + self.search_name_cb.setToolTip(t("Dateinamen durchsuchen")) self.search_content_cb = QCheckBox("Inhalt") self.search_content_cb.setChecked(True) self.search_content_cb.setAccessibleName("Im Dateiinhalt suchen") - self.search_content_cb.setToolTip("Volltextinhalte durchsuchen") + self.search_content_cb.setToolTip(t("Volltextinhalte durchsuchen")) self.search_path_cb = QCheckBox("Pfad") self.search_path_cb.setAccessibleName("Im Pfad suchen") - self.search_path_cb.setToolTip("Vollständigen Dateipfad durchsuchen") + self.search_path_cb.setToolTip(t("Vollständigen Dateipfad durchsuchen")) scope_layout.addWidget(self.search_name_cb) scope_layout.addWidget(self.search_content_cb) @@ -152,14 +153,14 @@ def _setup_ui(self): for name, _ in self.FILE_TYPES: self.type_combo.addItem(name) self.type_combo.setAccessibleName("Dateityp-Filter") - self.type_combo.setToolTip("Auf bestimmte Dateitypen einschränken") + self.type_combo.setToolTip(t("Auf bestimmte Dateitypen einschränken")) self.type_combo.currentIndexChanged.connect(self._on_type_changed) type_layout.addWidget(self.type_combo) self.custom_ext_input = QLineEdit() self.custom_ext_input.setPlaceholderText(".py, .txt, ...") self.custom_ext_input.setAccessibleName("Benutzerdefinierte Dateiendungen") - self.custom_ext_input.setToolTip("Dateiendungen kommagetrennt eingeben") + self.custom_ext_input.setToolTip(t("Dateiendungen kommagetrennt eingeben")) self.custom_ext_input.hide() type_layout.addWidget(self.custom_ext_input) @@ -171,7 +172,7 @@ def _setup_ui(self): for name, _, _ in self.DATE_PRESETS: self.date_preset_combo.addItem(name) self.date_preset_combo.setAccessibleName("Änderungszeitraum") - self.date_preset_combo.setToolTip("Zeitraum der letzten Änderung auswählen") + self.date_preset_combo.setToolTip(t("Zeitraum der letzten Änderung auswählen")) self.date_preset_combo.currentIndexChanged.connect(self._on_date_preset_changed) date_layout.addWidget(self.date_preset_combo) @@ -218,7 +219,7 @@ def _setup_ui(self): self.tags_input = QLineEdit() self.tags_input.setPlaceholderText("tag1, tag2, ... (mit Komma trennen)") self.tags_input.setAccessibleName("Datei-Tags filtern") - self.tags_input.setToolTip("Kommagetrennte Tags filtern") + self.tags_input.setToolTip(t("Kommagetrennte Tags filtern")) criteria_layout.addRow("Tags:", self.tags_input) layout.addWidget(criteria_group) @@ -229,13 +230,13 @@ def _setup_ui(self): self.search_btn = QPushButton("🔍 Suchen") self.search_btn.setDefault(True) self.search_btn.setAccessibleName("Suche ausführen") - self.search_btn.setToolTip("Startet die erweiterte Suche (Enter)") + self.search_btn.setToolTip(t("Startet die erweiterte Suche (Enter)")) self.search_btn.clicked.connect(self._do_search) button_layout.addWidget(self.search_btn) self.clear_btn = QPushButton("Zurücksetzen") self.clear_btn.setAccessibleName("Suchfilter zurücksetzen") - self.clear_btn.setToolTip("Setzt alle Kriterien und Filter auf Standardwerte zurück") + self.clear_btn.setToolTip(t("Setzt alle Kriterien und Filter auf Standardwerte zurück")) self.clear_btn.clicked.connect(self._reset_form) button_layout.addWidget(self.clear_btn) @@ -265,7 +266,7 @@ def _setup_ui(self): self.results_table.setSortingEnabled(True) self.results_table.setAccessibleName("Suchergebnisse") self.results_table.setAccessibleDescription("Liste gefundener Dateien mit Pfad, Typ, Größe und Datum.") - self.results_table.setToolTip("Ergebnistabelle (Doppelklick oder Enter zum Öffnen)") + self.results_table.setToolTip(t("Ergebnistabelle (Doppelklick oder Enter zum Öffnen)")) self.results_table.doubleClicked.connect(self._on_result_double_clicked) # Spaltenbreiten @@ -288,14 +289,14 @@ def _setup_ui(self): self.open_btn = QPushButton("Öffnen") self.open_btn.setAccessibleName("Ausgewählte Datei öffnen") - self.open_btn.setToolTip("Öffnet die ausgewählte Datei") + self.open_btn.setToolTip(t("Öffnet die ausgewählte Datei")) self.open_btn.clicked.connect(self._open_selected) self.open_btn.setEnabled(False) status_layout.addWidget(self.open_btn) self.open_folder_btn = QPushButton("Ordner öffnen") self.open_folder_btn.setAccessibleName("Enthaltenden Ordner öffnen") - self.open_folder_btn.setToolTip("Öffnet den Ordner der ausgewählten Datei") + self.open_folder_btn.setToolTip(t("Öffnet den Ordner der ausgewählten Datei")) self.open_folder_btn.clicked.connect(self._open_folder) self.open_folder_btn.setEnabled(False) status_layout.addWidget(self.open_folder_btn) @@ -309,7 +310,7 @@ def _setup_ui(self): close_btn = dialog_buttons.button(QDialogButtonBox.StandardButton.Close) if close_btn: close_btn.setAccessibleName("Dialog schließen") - close_btn.setToolTip("Schließt das Dialogfenster (Esc)") + close_btn.setToolTip(t("Schließt das Dialogfenster (Esc)")) dialog_buttons.rejected.connect(self.reject) layout.addWidget(dialog_buttons) diff --git a/src/main.py b/src/main.py index 660f258..46c99aa 100644 --- a/src/main.py +++ b/src/main.py @@ -72,6 +72,17 @@ def set_application_version(app: QApplication) -> None: app.setApplicationVersion(__version__) +def configure_application_language(): + """Restore the saved UI language before constructing any translated widgets.""" + from core.settings_manager import SettingsManager + from translator import get_translator, SUPPORTED_LANGUAGES + + language = SettingsManager.instance().get("general", "language", "de") + if language not in SUPPORTED_LANGUAGES: + language = "de" + return get_translator(language) + + def main(): """Haupteinstiegspunkt für ExplorerPro""" # High DPI Support @@ -84,8 +95,8 @@ def main(): try: app.setApplicationName("ExplorerPro") app.setOrganizationName("ExplorerPro") - from translator import get_translator - install_qt_translations(app, get_translator().get_language()) + translator = configure_application_language() + install_qt_translations(app, translator.get_language()) set_application_version(app) icon = load_app_icon() if not icon.isNull(): diff --git a/src/modules/launcher/apps_panel.py b/src/modules/launcher/apps_panel.py index a11bb6a..991dd06 100644 --- a/src/modules/launcher/apps_panel.py +++ b/src/modules/launcher/apps_panel.py @@ -22,6 +22,7 @@ import subprocess from core.platform_utils import open_path_with_system +from translator import t @dataclass @@ -149,11 +150,11 @@ def _setup_ui(self): self.path_edit = QLineEdit() self.path_edit.setAccessibleName("Programmpfad") self.path_edit.setAccessibleDescription("Dateipfad zur ausführbaren Datei oder zum Skript.") - self.path_edit.setToolTip("Pfad zur ausführbaren Datei") + self.path_edit.setToolTip(t("Pfad zur ausführbaren Datei")) path_btn = QPushButton("...") path_btn.setFixedWidth(30) path_btn.setAccessibleName("Programmdatei durchsuchen") - path_btn.setToolTip("Dateiauswahldialog öffnen") + path_btn.setToolTip(t("Dateiauswahldialog öffnen")) path_btn.clicked.connect(self._browse_path) path_layout.addWidget(self.path_edit) path_layout.addWidget(path_btn) @@ -163,7 +164,7 @@ def _setup_ui(self): self.category_combo.setEditable(True) self.category_combo.setAccessibleName("Kategorie") self.category_combo.setAccessibleDescription("Kategorie zur Gruppierung im Schnellstarter.") - self.category_combo.setToolTip("Kategorie auswählen oder neu eingeben") + self.category_combo.setToolTip(t("Kategorie auswählen oder neu eingeben")) self.category_combo.addItems([ "Allgemein", "Entwicklung", "Office", "Grafik", "Multimedia", "Internet", "System", "Spiele" @@ -192,7 +193,7 @@ def _setup_ui(self): ok_btn = buttons.button(QDialogButtonBox.StandardButton.Ok) if ok_btn: ok_btn.setAccessibleName("App speichern") - ok_btn.setToolTip("Speichert die Anwendungseinstellungen (Enter)") + ok_btn.setToolTip(t("Speichert die Anwendungseinstellungen (Enter)")) cancel_btn = buttons.button(QDialogButtonBox.StandardButton.Cancel) if cancel_btn: cancel_btn.setAccessibleName("Abbrechen") @@ -259,9 +260,9 @@ def _setup_ui(self): header = QHBoxLayout() self.search_edit = QLineEdit() self.search_edit.setPlaceholderText("🔍 App suchen...") - self.search_edit.setAccessibleName("Apps durchsuchen") + self.search_edit.setAccessibleName(t("Apps durchsuchen")) self.search_edit.setAccessibleDescription("Filtert die Anwendungsliste nach Name.") - self.search_edit.setToolTip("Apps durchsuchen") + self.search_edit.setToolTip(t("Apps durchsuchen")) self.search_edit.textChanged.connect(self._filter_apps) header.addWidget(self.search_edit) diff --git a/src/modules/prompts/prompts_panel.py b/src/modules/prompts/prompts_panel.py index 2097251..3351a47 100644 --- a/src/modules/prompts/prompts_panel.py +++ b/src/modules/prompts/prompts_panel.py @@ -18,6 +18,7 @@ from dataclasses import dataclass, field from datetime import datetime import json +from translator import t @dataclass @@ -76,7 +77,7 @@ def _setup_ui(self): self.category_combo = QComboBox() self.category_combo.setEditable(True) self.category_combo.setAccessibleName("Prompt-Kategorie") - self.category_combo.setToolTip("Kategorie für die Einordnung des Prompts") + self.category_combo.setToolTip(t("Kategorie für die Einordnung des Prompts")) self.category_combo.addItems(self.categories) form.addRow("Kategorie:", self.category_combo) @@ -84,7 +85,7 @@ def _setup_ui(self): self.tags_edit = QLineEdit() self.tags_edit.setPlaceholderText("tag1, tag2, tag3 (mit Komma trennen)") self.tags_edit.setAccessibleName("Prompt-Tags") - self.tags_edit.setToolTip("Schlagwörter zur Filterung, kommagetrennt") + self.tags_edit.setToolTip(t("Schlagwörter zur Filterung, kommagetrennt")) form.addRow("Tags:", self.tags_edit) layout.addLayout(form) @@ -98,7 +99,7 @@ def _setup_ui(self): self.content_edit.setAccessibleDescription( "Text der Prompt-Vorlage. Variablen in doppelten geschweiften Klammern werden beim Verwenden abgefragt." ) - self.content_edit.setToolTip("Vollständiger Prompt-Text") + self.content_edit.setToolTip(t("Vollständiger Prompt-Text")) layout.addWidget(self.content_edit) # Variablen-Hinweis @@ -284,7 +285,7 @@ def _setup_ui(self): self.preview_text.setReadOnly(True) self.preview_text.setMaximumHeight(100) self.preview_text.setAccessibleName("Prompt-Vorschautext") - self.preview_text.setToolTip("Vorschau des ausgewählten Prompts") + self.preview_text.setToolTip(t("Vorschau des ausgewählten Prompts")) preview_layout.addWidget(self.preview_text) # Aktions-Buttons @@ -299,7 +300,7 @@ def _setup_ui(self): self.edit_btn = QPushButton("✏️ Bearbeiten") self.edit_btn.setAccessibleName("Ausgewählten Prompt bearbeiten") - self.edit_btn.setToolTip("Öffnet den Bearbeitungsdialog für den Prompt") + self.edit_btn.setToolTip(t("Öffnet den Bearbeitungsdialog für den Prompt")) self.edit_btn.clicked.connect(self._edit_selected) self.edit_btn.setEnabled(False) btn_layout.addWidget(self.edit_btn) diff --git a/tests/test_gui_gc.py b/tests/test_gui_gc.py index eb0e946..1883937 100644 --- a/tests/test_gui_gc.py +++ b/tests/test_gui_gc.py @@ -184,7 +184,8 @@ class Translator: def get_language(self): return 'en' module = types.ModuleType('translator') -module.get_translator = lambda: Translator() +module.get_translator = lambda *args: Translator() +module.SUPPORTED_LANGUAGES = {'de': 'Deutsch', 'en': 'English'} sys.modules['translator'] = module def fail_setup(app, language): raise RuntimeError('application-setup-failed') diff --git a/tests/test_tooltip_language.py b/tests/test_tooltip_language.py new file mode 100644 index 0000000..61dc443 --- /dev/null +++ b/tests/test_tooltip_language.py @@ -0,0 +1,85 @@ +"""Language selection survives a restart and changes real Qt help text.""" + +import json +from pathlib import Path + +import pytest +import shiboken6 +from PySide6.QtWidgets import QApplication + +import manage_translations +import translator +from core.settings_manager import SettingsManager +from gui.settings_dialog import SettingsDialog +from src.main import configure_application_language + + +@pytest.fixture +def isolated_settings(monkeypatch, tmp_path): + # Use real JSON persistence without touching the user's settings or singleton. + monkeypatch.setattr(SettingsManager, "_instance", None) + monkeypatch.setattr(SettingsManager, "_get_config_path", lambda self: tmp_path / "settings.json") + monkeypatch.setattr(translator, "_default_translator", translator.TranslationSystem()) + return SettingsManager.instance() + + +@pytest.mark.parametrize("language", translator.SUPPORTED_LANGUAGES) +def test_language_survives_dialog_save_and_restart(isolated_settings, monkeypatch, language): + app = QApplication.instance() or QApplication([]) + settings = isolated_settings + dialog = SettingsDialog(settings=settings) + try: + dialog.language_cb.setCurrentIndex(dialog.language_cb.findData(language)) + dialog.apply_to_settings() + saved = json.loads(settings._config_path.read_text(encoding="utf-8")) + assert saved["general"]["language"] == language + finally: + shiboken6.delete(dialog) + + # Recreate the manager and restore the translator as at application startup. + monkeypatch.setattr(SettingsManager, "_instance", None) + restored = SettingsManager.instance() + tr = configure_application_language() + assert tr.get_language() == language + reopened = SettingsDialog(settings=restored) + try: + assert reopened.language_cb.currentData() == language + assert reopened.start_folder_edit.toolTip() == tr.translations["Standardverzeichnis beim Programmstart"][language] + assert reopened.language_cb.toolTip().splitlines()[1] == tr.translations["Sprachänderungen werden beim nächsten Start übernommen."][language] + assert app is QApplication.instance() + finally: + shiboken6.delete(reopened) + + +@pytest.mark.parametrize("invalid", ["fr", None, ["en"], {"language": "en"}]) +def test_invalid_saved_language_falls_back_to_german(isolated_settings, invalid): + app = QApplication.instance() or QApplication([]) + isolated_settings.set("general", "language", invalid) + assert configure_application_language().get_language() == "de" + dialog = SettingsDialog(settings=isolated_settings) + try: + assert dialog.language_cb.currentData() == "de" + assert dialog.start_folder_edit.toolTip() == "Standardverzeichnis beim Programmstart" + assert app is QApplication.instance() + finally: + shiboken6.delete(dialog) + + +def test_auditor_checks_translated_calls_without_modifying_catalog(tmp_path): + (tmp_path / "sample.py").write_text( + 'from translator import t\nbutton.setToolTip(t("Neue Hilfe"))\n' + 'button.setText(t("Status"))\n' + '# t("Nicht vorhandene Hilfe")\n', encoding="utf-8" + ) + locales = tmp_path / "locales" + locales.mkdir() + catalog = locales / "translations.json" + catalog.write_text("{}\n", encoding="utf-8") + assert manage_translations.find_german_strings(str(tmp_path)) == {"Neue Hilfe", "Status"} + assert manage_translations.manage_translations(str(tmp_path), check_mode=True) == 1 + assert catalog.read_text(encoding="utf-8") == "{}\n" + + +def test_repository_auditor_has_no_untranslated_help_text(): + root = Path(__file__).resolve().parents[1] + assert manage_translations.manage_translations(str(root), check_mode=True) == 0