From 9222c7586a5122c0383e2f016f01b5ce9e4c6453 Mon Sep 17 00:00:00 2001 From: Jann Stute Date: Sun, 22 Dec 2024 23:34:55 +0100 Subject: [PATCH 01/26] feat: implement Translator class --- tagstudio/src/qt/translations.py | 60 ++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 tagstudio/src/qt/translations.py diff --git a/tagstudio/src/qt/translations.py b/tagstudio/src/qt/translations.py new file mode 100644 index 000000000..5448d3926 --- /dev/null +++ b/tagstudio/src/qt/translations.py @@ -0,0 +1,60 @@ +from pathlib import Path + +import ujson +from PySide6.QtCore import QObject, Signal +from PySide6.QtGui import QAction +from PySide6.QtWidgets import QLabel, QPushButton + +DEFAULT_TRANSLATION = "en" + + +class TranslatedString(QObject): + changed = Signal(str) + + __default_value: str + __value: str | None = None + + def __init__(self, value: str): + super().__init__() + self.__default_value = value + + @property + def value(self) -> str: + return self.__value or self.__default_value + + @value.setter + def value(self, value: str): + if self.__value != value: + self.__value = value + self.changed.emit(self.__value) + + +class Translator: + _strings: dict[str, TranslatedString] = {} + + def __init__(self): + for k, v in self.__get_translation_dict(DEFAULT_TRANSLATION).items(): + self._strings[k] = TranslatedString(v) + + def __get_translation_dict(self, lang: str) -> dict[str, str]: + with open(Path(__file__).parents[2] / "resources" / "translations" / f"{lang}.json") as f: + return ujson.loads(f.read()) + + def change_language(self, lang: str): + translated = self.__get_translation_dict(lang) + for k in self._strings: + self._strings[k].value = translated.get(k, None) + + def translate_widget(self, widget: QObject, key: str): + if isinstance(widget, (QLabel, QAction, QPushButton)): + if key in self._strings: + self._strings[key].changed.connect(widget.setText) + widget.setText(self.translate(key)) + else: + raise RuntimeError + + def translate(self, key: str) -> str: + return self._strings[key].value if key in self._strings else "Not Translated" + + +Translations = Translator() From 96f9163af99d08f8237dcca1cdd372787466b60d Mon Sep 17 00:00:00 2001 From: Jann Stute Date: Mon, 23 Dec 2024 00:53:02 +0100 Subject: [PATCH 02/26] feat: add translate_with_setter and implement formatting of translations --- tagstudio/src/qt/translations.py | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/tagstudio/src/qt/translations.py b/tagstudio/src/qt/translations.py index 5448d3926..a1ee878c6 100644 --- a/tagstudio/src/qt/translations.py +++ b/tagstudio/src/qt/translations.py @@ -1,4 +1,5 @@ from pathlib import Path +from typing import Callable import ujson from PySide6.QtCore import QObject, Signal @@ -45,15 +46,32 @@ def change_language(self, lang: str): for k in self._strings: self._strings[k].value = translated.get(k, None) - def translate_widget(self, widget: QObject, key: str): + def translate_widget(self, widget: QObject, key: str, **kwargs): + """Translates the text of the QObject using :func:`translate_with_setter`.""" if isinstance(widget, (QLabel, QAction, QPushButton)): - if key in self._strings: - self._strings[key].changed.connect(widget.setText) - widget.setText(self.translate(key)) + self.translate_with_setter(widget.setText, key, **kwargs) + elif isinstance(widget, (QMenu)): + self.translate_with_setter(widget.setTitle, key, **kwargs) else: raise RuntimeError - def translate(self, key: str) -> str: + def translate_with_setter(self, setter: Callable[[str], None], key: str, **kwargs): + """Calls `setter` everytime the language changes and passes the translated string for `key`. + + Also formats the translation with the given keyword arguments. + """ + + def set_text(text: str): + setter(text.format(**kwargs)) + + if key in self._strings: + self._strings[key].changed.connect(set_text) + set_text(self.translate(key)) + + def translate(self, key: str, **kwargs) -> str: + return self[key].format(**kwargs) + + def __getitem__(self, key: str) -> str: return self._strings[key].value if key in self._strings else "Not Translated" From de57225c5e1899aefbd73694c385b38fc1496206 Mon Sep 17 00:00:00 2001 From: Jann Stute Date: Mon, 23 Dec 2024 01:17:55 +0100 Subject: [PATCH 03/26] feat: extend PanelModal to allow for translation --- tagstudio/src/qt/widgets/panel.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tagstudio/src/qt/widgets/panel.py b/tagstudio/src/qt/widgets/panel.py index b164acc93..d18efb347 100755 --- a/tagstudio/src/qt/widgets/panel.py +++ b/tagstudio/src/qt/widgets/panel.py @@ -16,8 +16,8 @@ class PanelModal(QWidget): def __init__( self, widget, - title: str, - window_title: str, + title: str = "", + window_title: str = "", done_callback: Callable | None = None, save_callback: Callable | None = None, has_save: bool = False, @@ -36,7 +36,7 @@ def __init__( self.title_widget.setObjectName("fieldTitle") self.title_widget.setWordWrap(True) self.title_widget.setStyleSheet("font-weight:bold;" "font-size:14px;" "padding-top: 6px") - self.title_widget.setText(title) + self.setTitle(title) self.title_widget.setAlignment(Qt.AlignmentFlag.AlignCenter) self.button_container = QWidget() @@ -95,6 +95,9 @@ def closeEvent(self, event): # noqa: N802 self.done_button.click() event.accept() + def setTitle(self, title: str): # noqa: N802 + self.title_widget.setText(title) + class PanelWidget(QWidget): """Used for widgets that go in a modal panel, ex. for editing or searching.""" From 9ab681febb8314bcdf7e4476887c039bf161e4a3 Mon Sep 17 00:00:00 2001 From: Jann Stute Date: Mon, 23 Dec 2024 01:32:34 +0100 Subject: [PATCH 04/26] feat: extend ProgressWidget to allow for translation --- tagstudio/src/qt/widgets/progress.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tagstudio/src/qt/widgets/progress.py b/tagstudio/src/qt/widgets/progress.py index f9247ebd7..d9bc09a5f 100644 --- a/tagstudio/src/qt/widgets/progress.py +++ b/tagstudio/src/qt/widgets/progress.py @@ -15,8 +15,9 @@ class ProgressWidget(QWidget): def __init__( self, - window_title: str, - label_text: str, + *, + window_title: str = "", + label_text: str = "", cancel_button_text: Optional[str], minimum: int, maximum: int, From c857eb4f1c3b7a26fca58d843b5ec623b0b01a23 Mon Sep 17 00:00:00 2001 From: Jann Stute Date: Mon, 23 Dec 2024 02:10:30 +0100 Subject: [PATCH 05/26] feat: translation in ts_qt.py --- tagstudio/resources/translations/en.json | 54 +++++-- tagstudio/src/qt/translations.py | 6 +- tagstudio/src/qt/ts_qt.py | 185 +++++++++++++++-------- 3 files changed, 166 insertions(+), 79 deletions(-) diff --git a/tagstudio/resources/translations/en.json b/tagstudio/resources/translations/en.json index 32cae5016..29fb89313 100644 --- a/tagstudio/resources/translations/en.json +++ b/tagstudio/resources/translations/en.json @@ -1,6 +1,7 @@ { "app.git": "Git Commit", "app.pre_release": "Pre-Release", + "app.title": "{base_title} - Library '{library_dir}'", "edit.tag_manager": "Manage Tags", "entries.duplicate.merge.label": "Merging Duplicate Entries", "entries.duplicate.merge": "Merge Duplicate Entries", @@ -69,6 +70,11 @@ "home.search_library": "Search Library", "home.search_tags": "Search Tags", "home.search": "Search", + "home.thumbnail_size.extra_large": "Extra Large Thumbnails", + "home.thumbnail_size.large": "Large Thumbnails", + "home.thumbnail_size.medium": "Medium Thumbnails", + "home.thumbnail_size.small": "Small Thumbnails", + "home.thumbnail_size.mini": "Mini Thumbnails", "home.thumbnail_size": "Thumbnail Size", "ignore_list.add_extension": "Add Extension", "ignore_list.mode.exclude": "Exclude", @@ -82,23 +88,34 @@ "library.missing": "Library Location is Missing", "library.name": "Library", "library.refresh.scanning_preparing": "Scanning Directories for New Files...\nPreparing...", - "library.refresh.scanning": "Scanning Directories for New Files...\n%{x + 1} File%{\"s\" if x + 1 != 1 else \"\"} Searched, %{len(self.lib.files_not_in_library)} New Files Found", + "library.refresh.scanning.plural": "Scanning Directories for New Files...\n{searched_count} Files Searched, {found_count} New Files Found", + "library.refresh.scanning.singular": "Scanning Directories for New Files...\n{searched_count} File Searched, {found_count} New Files Found", "library.refresh.title": "Refreshing Directories", "library.scan_library.title": "Scanning Library", - "macros.running.dialog.new_entries": "Running Configured Macros on %{x + 1}/%{len(new_ids)} New Entries", + "macros.running.dialog.new_entries": "Running Configured Macros on {count}/{total} New Entries", "macros.running.dialog.title": "Running Macros on New Entries", "menu.edit.ignore_list": "Ignore Files and Folders", - "menu.edit": "Edit", + "menu.edit.manage_file_extensions": "Manage File Extensions", + "menu.edit.manage_tags": "Manage Tags", + "menu.edit.new_tag": "New &Tag", + "menu.edit": "&Edit", + "menu.file.close_library": "&Close Library", "menu.file.new_library": "New Library", - "menu.file.open_create_library": "Open/Create Library", + "menu.file.open_create_library": "&Open/Create Library", "menu.file.open_library": "Open Library", "menu.file.save_library": "Save Library", - "menu.file": "File", - "menu.help": "Help", - "menu.macros": "Macros", + "menu.file.save_backup": "&Save Library Backup", + "menu.file.refresh_directories": "&Refresh Directories", + "menu.file": "&File", + "menu.help": "&Help", + "menu.macros.autofill": "Autofill", + "menu.macros.folders_to_tags": "Folders to Tags", + "menu.macros": "&Macros", "menu.select": "Select", - "menu.tools": "Tools", - "menu.view": "View", + "menu.tools.fix_duplicate_files": "Fix Duplicate &Files", + "menu.tools.fix_unlinked_entries": "Fix &Unlinked Entries", + "menu.tools": "&Tools", + "menu.view": "&View", "menu.window": "Window", "preview.no_selection": "No Items Selected", "select.all": "Select All", @@ -106,11 +123,14 @@ "settings.open_library_on_start": "Open Library on Start", "settings.show_filenames_in_grid": "Show Filenames in Grid", "settings.show_recent_libraries": "Show Recent Libraries", - "splash.opening_library": "Opening Library", - "status.library_backup_success": "Library Backup Saved at:", + "splash.opening_library": "Opening Library \"{library_path}\"...", + "status.library_closing": "Closing Library...", + "status.library_closed": "Library Closed ({time_span})", + "status.library_backup_in_progress": "Saving Library Backup...", + "status.library_backup_success": "Library Backup Saved at: \"{path}\" ({time_span})", "status.library_save_success": "Library Saved and Closed!", - "status.library_search_query": "Searching Library for", - "status.results_found": "{results.total_count} Results Found", + "status.library_search_query": "Searching Library...", + "status.results_found": "{count} Results Found ({time_span})", "status.results": "Results", "tag_manager.title": "Library Tags", "tag.add_to_search": "Add to Search", @@ -128,5 +148,9 @@ "view.size.1": "Small", "view.size.2": "Medium", "view.size.3": "Large", - "view.size.4": "Extra Large" -} + "view.size.4": "Extra Large", + "window.button.close": "Close", + "window.title.error": "Error", + "window.title.open_create_library": "Open/Create Library", + "window.message.error_opening_library": "Error opening library." +} \ No newline at end of file diff --git a/tagstudio/src/qt/translations.py b/tagstudio/src/qt/translations.py index a1ee878c6..b3ec9ca03 100644 --- a/tagstudio/src/qt/translations.py +++ b/tagstudio/src/qt/translations.py @@ -46,7 +46,7 @@ def change_language(self, lang: str): for k in self._strings: self._strings[k].value = translated.get(k, None) - def translate_widget(self, widget: QObject, key: str, **kwargs): + def translate_qobject(self, widget: QObject, key: str, **kwargs): """Translates the text of the QObject using :func:`translate_with_setter`.""" if isinstance(widget, (QLabel, QAction, QPushButton)): self.translate_with_setter(widget.setText, key, **kwargs) @@ -66,9 +66,9 @@ def set_text(text: str): if key in self._strings: self._strings[key].changed.connect(set_text) - set_text(self.translate(key)) + set_text(self.translate_formatted(key)) - def translate(self, key: str, **kwargs) -> str: + def translate_formatted(self, key: str, **kwargs) -> str: return self[key].format(**kwargs) def __getitem__(self, key: str) -> str: diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index 4a568b578..a6c205c93 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -91,6 +91,8 @@ from src.qt.widgets.progress import ProgressWidget from src.qt.widgets.thumb_renderer import ThumbRenderer +from .translations import Translations + # SIGQUIT is not defined on Windows if sys.platform == "win32": from signal import SIGINT, SIGTERM, signal @@ -186,10 +188,10 @@ def init_workers(self): def open_library_from_dialog(self): dir = QFileDialog.getExistingDirectory( - None, - "Open/Create Library", - "/", - QFileDialog.Option.ShowDirsOnly, + parent=None, + caption=Translations["window.title.open_create_library"], + dir="/", + options=QFileDialog.Option.ShowDirsOnly, ) if dir not in (None, ""): self.open_library(Path(dir)) @@ -254,15 +256,22 @@ def start(self) -> None: self.main_window.setMenuBar(menu_bar) menu_bar.setNativeMenuBar(True) - file_menu = QMenu("&File", menu_bar) - edit_menu = QMenu("&Edit", menu_bar) - view_menu = QMenu("&View", menu_bar) - tools_menu = QMenu("&Tools", menu_bar) - macros_menu = QMenu("&Macros", menu_bar) - help_menu = QMenu("&Help", menu_bar) + file_menu = QMenu(menu_bar) + Translations.translate_qobject(file_menu, "menu.file") + edit_menu = QMenu(menu_bar) + Translations.translate_qobject(edit_menu, "menu.edit") + view_menu = QMenu(menu_bar) + Translations.translate_qobject(view_menu, "menu.view") + tools_menu = QMenu(menu_bar) + Translations.translate_qobject(tools_menu, "menu.tools") + macros_menu = QMenu(menu_bar) + Translations.translate_qobject(macros_menu, "menu.macros") + help_menu = QMenu(menu_bar) + Translations.translate_qobject(help_menu, "menu.help") # File Menu ============================================================ - open_library_action = QAction("&Open/Create Library", menu_bar) + open_library_action = QAction(menu_bar) + Translations.translate_qobject(open_library_action, "menu.file.open_create_library") open_library_action.triggered.connect(lambda: self.open_library_from_dialog()) open_library_action.setShortcut( QtCore.QKeyCombination( @@ -273,7 +282,8 @@ def start(self) -> None: open_library_action.setToolTip("Ctrl+O") file_menu.addAction(open_library_action) - save_library_backup_action = QAction("&Save Library Backup", menu_bar) + save_library_backup_action = QAction(menu_bar) + Translations.translate_qobject(save_library_backup_action, "menu.file.save_backup") save_library_backup_action.triggered.connect( lambda: self.callback_library_needed_check(self.backup_library) ) @@ -291,7 +301,8 @@ def start(self) -> None: file_menu.addSeparator() - add_new_files_action = QAction("&Refresh Directories", menu_bar) + add_new_files_action = QAction(menu_bar) + Translations.translate_qobject(add_new_files_action, "menu.file.refresh_directories") add_new_files_action.triggered.connect( lambda: self.callback_library_needed_check(self.add_new_files_callback) ) @@ -305,12 +316,14 @@ def start(self) -> None: file_menu.addAction(add_new_files_action) file_menu.addSeparator() - close_library_action = QAction("&Close Library", menu_bar) + close_library_action = QAction(menu_bar) + Translations.translate_qobject(close_library_action, "menu.file.close_library") close_library_action.triggered.connect(self.close_library) file_menu.addAction(close_library_action) file_menu.addSeparator() - open_on_start_action = QAction("Open Library on Start", self) + open_on_start_action = QAction(self) + Translations.translate_qobject(open_on_start_action, "settings.open_library_on_start") open_on_start_action.setCheckable(True) open_on_start_action.setChecked( bool(self.settings.value(SettingItems.START_LOAD_LAST, defaultValue=True, type=bool)) @@ -321,7 +334,8 @@ def start(self) -> None: file_menu.addAction(open_on_start_action) # Edit Menu ============================================================ - new_tag_action = QAction("New &Tag", menu_bar) + new_tag_action = QAction(menu_bar) + Translations.translate_qobject(new_tag_action, "menu.edit.new_tag") new_tag_action.triggered.connect(lambda: self.add_tag_action_callback()) new_tag_action.setShortcut( QtCore.QKeyCombination( @@ -334,7 +348,8 @@ def start(self) -> None: edit_menu.addSeparator() - select_all_action = QAction("Select All", menu_bar) + select_all_action = QAction(menu_bar) + Translations.translate_qobject(select_all_action, "select.all") select_all_action.triggered.connect(self.select_all_action_callback) select_all_action.setShortcut( QtCore.QKeyCombination( @@ -345,7 +360,8 @@ def start(self) -> None: select_all_action.setToolTip("Ctrl+A") edit_menu.addAction(select_all_action) - clear_select_action = QAction("Clear Selection", menu_bar) + clear_select_action = QAction(menu_bar) + Translations.translate_qobject(clear_select_action, "select.clear") clear_select_action.triggered.connect(self.clear_select_action_callback) clear_select_action.setShortcut(QtCore.Qt.Key.Key_Escape) clear_select_action.setToolTip("Esc") @@ -353,16 +369,21 @@ def start(self) -> None: edit_menu.addSeparator() - manage_file_extensions_action = QAction("Manage File Extensions", menu_bar) + manage_file_extensions_action = QAction(menu_bar) + Translations.translate_qobject( + manage_file_extensions_action, "menu.edit.manage_file_extensions" + ) manage_file_extensions_action.triggered.connect(self.show_file_extension_modal) edit_menu.addAction(manage_file_extensions_action) - tag_database_action = QAction("Manage Tags", menu_bar) + tag_database_action = QAction(menu_bar) + Translations.translate_qobject(tag_database_action, "menu.edit.manage_tags") tag_database_action.triggered.connect(lambda: self.show_tag_database()) edit_menu.addAction(tag_database_action) # View Menu ============================================================ - show_libs_list_action = QAction("Show Recent Libraries", menu_bar) + show_libs_list_action = QAction(menu_bar) + Translations.translate_qobject(show_libs_list_action, "settings.show_recent_libraries") show_libs_list_action.setCheckable(True) show_libs_list_action.setChecked( bool(self.settings.value(SettingItems.WINDOW_SHOW_LIBS, defaultValue=True, type=bool)) @@ -375,7 +396,8 @@ def start(self) -> None: ) view_menu.addAction(show_libs_list_action) - show_filenames_action = QAction("Show Filenames in Grid", menu_bar) + show_filenames_action = QAction(menu_bar) + Translations.translate_qobject(show_filenames_action, "settings.show_filenames_in_grid") show_filenames_action.setCheckable(True) show_filenames_action.setChecked( bool(self.settings.value(SettingItems.SHOW_FILENAMES, defaultValue=True, type=bool)) @@ -394,7 +416,10 @@ def create_fix_unlinked_entries_modal(): self.unlinked_modal = FixUnlinkedEntriesModal(self.lib, self) self.unlinked_modal.show() - fix_unlinked_entries_action = QAction("Fix &Unlinked Entries", menu_bar) + fix_unlinked_entries_action = QAction(menu_bar) + Translations.translate_qobject( + fix_unlinked_entries_action, "menu.tools.fix_unlinked_entries" + ) fix_unlinked_entries_action.triggered.connect(create_fix_unlinked_entries_modal) tools_menu.addAction(fix_unlinked_entries_action) @@ -403,7 +428,8 @@ def create_dupe_files_modal(): self.dupe_modal = FixDupeFilesModal(self.lib, self) self.dupe_modal.show() - fix_dupe_files_action = QAction("Fix Duplicate &Files", menu_bar) + fix_dupe_files_action = QAction(menu_bar) + Translations.translate_qobject(fix_dupe_files_action, "menu.tools.fix_duplicate_files") fix_dupe_files_action.triggered.connect(create_dupe_files_modal) tools_menu.addAction(fix_dupe_files_action) @@ -412,7 +438,8 @@ def create_dupe_files_modal(): # tools_menu.addAction(create_collage_action) # Macros Menu ========================================================== - self.autofill_action = QAction("Autofill", menu_bar) + self.autofill_action = QAction(menu_bar) + Translations.translate_qobject(self.autofill_action, "menu.macros.autofill") self.autofill_action.triggered.connect( lambda: ( self.run_macros(MacroID.AUTOFILL, self.selected), @@ -426,12 +453,14 @@ def create_folders_tags_modal(): self.folders_modal = FoldersToTagsModal(self.lib, self) self.folders_modal.show() - folders_to_tags_action = QAction("Folders to Tags", menu_bar) + folders_to_tags_action = QAction(menu_bar) + Translations.translate_qobject(folders_to_tags_action, "menu.macros.folders_to_tags") folders_to_tags_action.triggered.connect(create_folders_tags_modal) macros_menu.addAction(folders_to_tags_action) # Help Menu ============================================================ - self.repo_action = QAction("Visit GitHub Repository", menu_bar) + self.repo_action = QAction(menu_bar) + Translations.translate_qobject(self.repo_action, "help.visit_github") self.repo_action.triggered.connect( lambda: webbrowser.open("https://github.com/TagStudioDev/TagStudio") ) @@ -455,12 +484,13 @@ def create_folders_tags_modal(): str(Path(__file__).parents[2] / "resources/qt/fonts/Oxanium-Bold.ttf") ) + # TODO this doesn't update when the language is changed self.thumb_sizes: list[tuple[str, int]] = [ - ("Extra Large Thumbnails", 256), - ("Large Thumbnails", 192), - ("Medium Thumbnails", 128), - ("Small Thumbnails", 96), - ("Mini Thumbnails", 76), + (Translations["home.thumbnail_size.extra_large"], 256), + (Translations["home.thumbnail_size.large"], 192), + (Translations["home.thumbnail_size.medium"], 128), + (Translations["home.thumbnail_size.small"], 96), + (Translations["home.thumbnail_size.mini"], 76), ] self.item_thumbs: list[ItemThumb] = [] self.thumb_renderers: list[ThumbRenderer] = [] @@ -472,7 +502,9 @@ def create_folders_tags_modal(): # check status of library path evaluating if path_result.success and path_result.library_path: self.splash.showMessage( - f'Opening Library "{path_result.library_path}"...', + Translations.translate_formatted( + "splash.opening_library", library_path=path_result.library_path + ), int(Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignHCenter), QColor("#9782ff"), ) @@ -489,8 +521,8 @@ def show_error_message(self, message: str): msg_box = QMessageBox() msg_box.setIcon(QMessageBox.Icon.Critical) msg_box.setText(message) - msg_box.setWindowTitle("Error") - msg_box.addButton("Close", QMessageBox.ButtonRole.AcceptRole) + msg_box.setWindowTitle(Translations["window.title.error"]) + msg_box.addButton(Translations["window.button.close"], QMessageBox.ButtonRole.AcceptRole) # Show the message box msg_box.exec() @@ -584,7 +616,7 @@ def close_library(self, is_shutdown: bool = False): return logger.info("Closing Library...") - self.main_window.statusbar.showMessage("Closing Library...") + self.main_window.statusbar.showMessage(Translations["status.library_closing"]) start_time = time.time() self.settings.setValue(SettingItems.LAST_LIBRARY, str(self.lib.library_dir)) @@ -610,26 +642,32 @@ def close_library(self, is_shutdown: bool = False): end_time = time.time() self.main_window.statusbar.showMessage( - f"Library Closed ({format_timespan(end_time - start_time)})" + Translations.translate_formatted( + "status.library_closed", time_span=format_timespan(end_time - start_time) + ) ) def backup_library(self): logger.info("Backing Up Library...") - self.main_window.statusbar.showMessage("Saving Library...") + self.main_window.statusbar.showMessage(Translations["status.library_backup_in_progress"]) start_time = time.time() target_path = self.lib.save_library_backup_to_disk() end_time = time.time() self.main_window.statusbar.showMessage( - f'Library Backup Saved at: "{target_path}" ({format_timespan(end_time - start_time)})' + Translations.translate_formatted( + "status.library_backup_success", + path=target_path, + time_span=format_timespan(end_time - start_time), + ) ) def add_tag_action_callback(self): self.modal = PanelModal( BuildTagPanel(self.lib), - "New Tag", - "Add Tag", has_save=True, ) + Translations.translate_with_setter(self.modal.setTitle, "tag.new") + Translations.translate_with_setter(self.modal.setWindowTitle, "tag.add") panel: BuildTagPanel = self.modal.widget self.modal.saved.connect( @@ -662,21 +700,21 @@ def clear_select_action_callback(self): def show_tag_database(self): self.modal = PanelModal( widget=TagDatabasePanel(self.lib), - title="Library Tags", - window_title="Library Tags", done_callback=self.preview_panel.update_widgets, has_save=False, ) + Translations.translate_with_setter(self.modal.setTitle, "tag_manager.title") + Translations.translate_with_setter(self.modal.setWindowTitle, "tag_manager.title") self.modal.show() def show_file_extension_modal(self): panel = FileExtensionModal(self.lib) self.modal = PanelModal( panel, - "File Extensions", - "File Extensions", has_save=True, ) + Translations.translate_with_setter(self.modal.setTitle, "ignore_list.title") + Translations.translate_with_setter(self.modal.setWindowTitle, "ignore_list.title") self.modal.saved.connect(lambda: (panel.save(), self.filter_items())) self.modal.show() @@ -686,12 +724,13 @@ def add_new_files_callback(self): tracker = RefreshDirTracker(self.lib) pw = ProgressWidget( - window_title="Refreshing Directories", - label_text="Scanning Directories for New Files...\nPreparing...", cancel_button_text=None, minimum=0, maximum=0, ) + Translations.translate_with_setter(pw.setWindowTitle, "library.refresh.title") + Translations.translate_with_setter(pw.update_label, "library.refresh.scanning_preparing") + pw.show() iterator = FunctionIterator(lambda: tracker.refresh_dir(self.lib.library_dir)) @@ -699,9 +738,13 @@ def add_new_files_callback(self): lambda x: ( pw.update_progress(x + 1), pw.update_label( - f"Scanning Directories for New Files...\n{x + 1}" - f' File{"s" if x + 1 != 1 else ""} Searched,' - f" {tracker.files_count} New Files Found" + Translations.translate_formatted( + "library.refresh.scanning.plural" + if x + 1 != 1 + else "library.refresh.scanning.singular", + searched_count=x + 1, + found_count=tracker.files_count, + ) ), ) ) @@ -724,18 +767,24 @@ def add_new_files_runnable(self, tracker: RefreshDirTracker): iterator = FunctionIterator(tracker.save_new_files) pw = ProgressWidget( - window_title="Running Macros on New Entries", - label_text=f"Running Configured Macros on 1/{files_count} New Entries", cancel_button_text=None, minimum=0, maximum=files_count, ) + Translations.translate_with_setter(pw.setWindowTitle, "macros.running.dialog.title") + Translations.translate_with_setter( + pw.update_label, "macros.running.dialog.new_entries", count=1, total=files_count + ) pw.show() iterator.value.connect( lambda x: ( pw.update_progress(x + 1), - pw.update_label(f"Running Configured Macros on {x + 1}/{files_count} New Entries"), + pw.update_label( + Translations.translate_formatted( + "macros.running.dialog.new_entries", count=x + 1, total=files_count + ) + ), ) ) r = CustomRunnable(iterator.run) @@ -1131,7 +1180,7 @@ def filter_items(self, filter: FilterState | None = None) -> None: self.filter = dataclasses.replace(self.filter, **dataclasses.asdict(filter)) # inform user about running search - self.main_window.statusbar.showMessage("Searching Library...") + self.main_window.statusbar.showMessage(Translations["status.library_search_query"]) self.main_window.statusbar.repaint() # search the library @@ -1146,7 +1195,11 @@ def filter_items(self, filter: FilterState | None = None) -> None: # inform user about completed search self.main_window.statusbar.showMessage( - f"{results.total_count} Results Found ({format_timespan(end_time - start_time)})" + Translations.translate_formatted( + "status.results_found", + count=results.total_count, + time_span=format_timespan(end_time - start_time), + ) ) # update page content @@ -1193,9 +1246,13 @@ def update_libs_list(self, path: Path | str): def open_library(self, path: Path) -> None: """Open a TagStudio library.""" - open_message: str = f'Opening Library "{str(path)}"...' - self.main_window.landing_widget.set_status_label(open_message) - self.main_window.statusbar.showMessage(open_message, 3) + translation_params = {"key": "splash.opening_library", "library_path": str(path)} + Translations.translate_with_setter( + self.main_window.landing_widget.set_status_label, **translation_params + ) + self.main_window.statusbar.showMessage( + Translations.translate_formatted(**translation_params), 3 + ) self.main_window.repaint() open_status: LibraryStatus = self.lib.open_library(path) @@ -1213,7 +1270,9 @@ def open_library(self, path: Path) -> None: def init_library(self, path: Path, open_status: LibraryStatus): if not open_status.success: - self.show_error_message(open_status.message or "Error opening library.") + self.show_error_message( + open_status.message or Translations["window.message.error_opening_library"] + ) return open_status self.init_workers() @@ -1225,8 +1284,12 @@ def init_library(self, path: Path, open_status: LibraryStatus): self.add_new_files_callback() self.update_libs_list(path) - title_text = f"{self.base_title} - Library '{self.lib.library_dir}'" - self.main_window.setWindowTitle(title_text) + Translations.translate_with_setter( + self.main_window.setWindowTitle, + "app.title", + base_title=self.base_title, + library_dir=self.lib.library_dir, + ) self.main_window.setAcceptDrops(True) self.selected.clear() From bf5d18a4229c3691d928c9771413fdd23772a787 Mon Sep 17 00:00:00 2001 From: Jann Stute Date: Mon, 23 Dec 2024 02:25:13 +0100 Subject: [PATCH 06/26] debug: set default lang to DE and show "Not Translated" when replacing untranslated stuff --- tagstudio/src/qt/translations.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tagstudio/src/qt/translations.py b/tagstudio/src/qt/translations.py index b3ec9ca03..057c5da79 100644 --- a/tagstudio/src/qt/translations.py +++ b/tagstudio/src/qt/translations.py @@ -4,9 +4,9 @@ import ujson from PySide6.QtCore import QObject, Signal from PySide6.QtGui import QAction -from PySide6.QtWidgets import QLabel, QPushButton +from PySide6.QtWidgets import QLabel, QMenu, QPushButton -DEFAULT_TRANSLATION = "en" +DEFAULT_TRANSLATION = "de" class TranslatedString(QObject): @@ -18,6 +18,7 @@ class TranslatedString(QObject): def __init__(self, value: str): super().__init__() self.__default_value = value + self.__value = self.__default_value # TODO remove this line @property def value(self) -> str: @@ -26,7 +27,7 @@ def value(self) -> str: @value.setter def value(self, value: str): if self.__value != value: - self.__value = value + self.__value = value or "Not Translated" # TODO remove `or "Not Translated"` self.changed.emit(self.__value) From a38847cb7ac99e48c1dae513c8014850db4dbd8e Mon Sep 17 00:00:00 2001 From: Jann Stute Date: Mon, 23 Dec 2024 17:56:45 +0100 Subject: [PATCH 07/26] add translation todos --- tagstudio/src/qt/main_window.py | 10 +-- tagstudio/src/qt/modals/add_field.py | 8 +- tagstudio/src/qt/modals/build_tag.py | 16 ++-- tagstudio/src/qt/modals/delete_unlinked.py | 14 ++-- tagstudio/src/qt/modals/drop_import.py | 24 +++--- tagstudio/src/qt/modals/file_extension.py | 10 +-- tagstudio/src/qt/modals/fix_dupes.py | 30 +++---- tagstudio/src/qt/modals/fix_unlinked.py | 24 +++--- tagstudio/src/qt/modals/folders_to_tags.py | 10 +-- tagstudio/src/qt/modals/merge_dupe_entries.py | 4 +- tagstudio/src/qt/modals/mirror_entities.py | 12 +-- tagstudio/src/qt/modals/relink_unlinked.py | 6 +- tagstudio/src/qt/modals/tag_database.py | 16 ++-- tagstudio/src/qt/modals/tag_search.py | 2 +- tagstudio/src/qt/platform_strings.py | 4 +- tagstudio/src/qt/ui/home_ui.py | 8 +- tagstudio/src/qt/widgets/item_thumb.py | 4 +- tagstudio/src/qt/widgets/landing.py | 2 +- tagstudio/src/qt/widgets/migration_modal.py | 84 ++++++++++--------- tagstudio/src/qt/widgets/panel.py | 6 +- tagstudio/src/qt/widgets/preview_panel.py | 59 +++++++------ tagstudio/src/qt/widgets/tag.py | 6 +- tagstudio/src/qt/widgets/tag_box.py | 4 +- tagstudio/src/qt/widgets/video_player.py | 4 +- 24 files changed, 190 insertions(+), 177 deletions(-) diff --git a/tagstudio/src/qt/main_window.py b/tagstudio/src/qt/main_window.py index 3de0118d8..4d755f051 100644 --- a/tagstudio/src/qt/main_window.py +++ b/tagstudio/src/qt/main_window.py @@ -196,21 +196,21 @@ def retranslateUi(self, MainWindow): "MainWindow", u"MainWindow", None)) # Navigation buttons self.backButton.setText( - QCoreApplication.translate("MainWindow", u"<", None)) + QCoreApplication.translate("MainWindow", u"<", None)) #TODO translate self.forwardButton.setText( - QCoreApplication.translate("MainWindow", u">", None)) + QCoreApplication.translate("MainWindow", u">", None)) #TODO translate # Search field self.searchField.setPlaceholderText( - QCoreApplication.translate("MainWindow", u"Search Entries", None)) + QCoreApplication.translate("MainWindow", u"Search Entries", None)) #TODO translate self.searchButton.setText( - QCoreApplication.translate("MainWindow", u"Search", None)) + QCoreApplication.translate("MainWindow", u"Search", None)) #TODO translate self.thumb_size_combobox.setCurrentText("") # Thumbnail size selector self.thumb_size_combobox.setPlaceholderText( - QCoreApplication.translate("MainWindow", u"Thumbnail Size", None)) + QCoreApplication.translate("MainWindow", u"Thumbnail Size", None)) #TODO translate # retranslateUi def moveEvent(self, event) -> None: diff --git a/tagstudio/src/qt/modals/add_field.py b/tagstudio/src/qt/modals/add_field.py index fd0e0c997..5e0a054dc 100644 --- a/tagstudio/src/qt/modals/add_field.py +++ b/tagstudio/src/qt/modals/add_field.py @@ -26,7 +26,7 @@ def __init__(self, library: Library): super().__init__() self.is_connected = False self.lib = library - self.setWindowTitle("Add Field") + self.setWindowTitle("Add Field") # TODO translate self.setWindowModality(Qt.WindowModality.ApplicationModal) self.setMinimumSize(400, 300) self.root_layout = QVBoxLayout(self) @@ -40,7 +40,7 @@ def __init__(self, library: Library): # 'text-align:center;' "font-weight:bold;" "font-size:14px;" "padding-top: 6px" "" ) - self.title_widget.setText("Add Field") + self.title_widget.setText("Add Field") # TODO translate self.title_widget.setAlignment(Qt.AlignmentFlag.AlignCenter) self.list_widget = QListWidget() @@ -54,13 +54,13 @@ def __init__(self, library: Library): # self.cancel_button.setText('Cancel') self.cancel_button = QPushButton() - self.cancel_button.setText("Cancel") + self.cancel_button.setText("Cancel") # TODO translate self.cancel_button.clicked.connect(self.hide) # self.cancel_button.clicked.connect(widget.reset) self.button_layout.addWidget(self.cancel_button) self.save_button = QPushButton() - self.save_button.setText("Add") + self.save_button.setText("Add") # TODO translate # self.save_button.setAutoDefault(True) self.save_button.setDefault(True) self.save_button.clicked.connect(self.hide) diff --git a/tagstudio/src/qt/modals/build_tag.py b/tagstudio/src/qt/modals/build_tag.py index fcb786ae1..8e77248db 100644 --- a/tagstudio/src/qt/modals/build_tag.py +++ b/tagstudio/src/qt/modals/build_tag.py @@ -59,7 +59,7 @@ def __init__(self, library: Library, tag: Tag | None = None): self.name_field = QLineEdit() self.name_field.setFixedHeight(24) self.name_field.textChanged.connect(self.on_name_changed) - self.name_field.setPlaceholderText("Tag Name (Required)") + self.name_field.setPlaceholderText("Tag Name (Required)") # TODO translate self.name_layout.addWidget(self.name_field) # Shorthand ------------------------------------------------------------ @@ -70,7 +70,7 @@ def __init__(self, library: Library, tag: Tag | None = None): self.shorthand_layout.setSpacing(0) self.shorthand_layout.setAlignment(Qt.AlignmentFlag.AlignLeft) self.shorthand_title = QLabel() - self.shorthand_title.setText("Shorthand") + self.shorthand_title.setText("Shorthand") # TODO translate self.shorthand_layout.addWidget(self.shorthand_title) self.shorthand_field = QLineEdit() self.shorthand_layout.addWidget(self.shorthand_field) @@ -83,7 +83,7 @@ def __init__(self, library: Library, tag: Tag | None = None): self.aliases_layout.setSpacing(0) self.aliases_layout.setAlignment(Qt.AlignmentFlag.AlignLeft) self.aliases_title = QLabel() - self.aliases_title.setText("Aliases") + self.aliases_title.setText("Aliases") # TODO translate self.aliases_layout.addWidget(self.aliases_title) self.aliases_flow_widget = QWidget() @@ -134,7 +134,7 @@ def __init__(self, library: Library, tag: Tag | None = None): self.subtags_layout.setAlignment(Qt.AlignmentFlag.AlignLeft) self.subtags_title = QLabel() - self.subtags_title.setText("Parent Tags") + self.subtags_title.setText("Parent Tags") # TODO translate self.subtags_layout.addWidget(self.subtags_title) self.subtag_flow_widget = QWidget() @@ -180,7 +180,7 @@ def __init__(self, library: Library, tag: Tag | None = None): tsp = TagSearchPanel(self.lib, exclude_ids) tsp.tag_chosen.connect(lambda x: self.add_subtag_callback(x)) - self.add_tag_modal = PanelModal(tsp, "Add Parent Tags", "Add Parent Tags") + self.add_tag_modal = PanelModal(tsp, "Add Parent Tags", "Add Parent Tags") # TODO translate self.subtags_add_button.clicked.connect(self.add_tag_modal.show) # self.subtags_layout.addWidget(self.subtags_add_button) @@ -196,7 +196,7 @@ def __init__(self, library: Library, tag: Tag | None = None): self.color_layout.setSpacing(0) self.color_layout.setAlignment(Qt.AlignmentFlag.AlignLeft) self.color_title = QLabel() - self.color_title.setText("Color") + self.color_title.setText("Color") # TODO translate self.color_layout.addWidget(self.color_title) self.color_field = QComboBox() self.color_field.setEditable(False) @@ -218,7 +218,7 @@ def __init__(self, library: Library, tag: Tag | None = None): ) ) self.color_layout.addWidget(self.color_field) - remove_selected_alias_action = QAction("remove selected alias", self) + remove_selected_alias_action = QAction("remove selected alias", self) # TODO translate remove_selected_alias_action.triggered.connect(self.remove_selected_alias) remove_selected_alias_action.setShortcut( QtCore.QKeyCombination( @@ -243,7 +243,7 @@ def __init__(self, library: Library, tag: Tag | None = None): self.alias_names: set[str] = set() self.new_alias_names: dict = dict() - self.set_tag(tag or Tag(name="New Tag")) + self.set_tag(tag or Tag(name="New Tag")) # TODO translate if tag is None: self.name_field.selectAll() diff --git a/tagstudio/src/qt/modals/delete_unlinked.py b/tagstudio/src/qt/modals/delete_unlinked.py index b382d0831..b1e1605ce 100644 --- a/tagstudio/src/qt/modals/delete_unlinked.py +++ b/tagstudio/src/qt/modals/delete_unlinked.py @@ -29,7 +29,7 @@ def __init__(self, driver: "QtDriver", tracker: MissingRegistry): super().__init__() self.driver = driver self.tracker = tracker - self.setWindowTitle("Delete Unlinked Entries") + self.setWindowTitle("Delete Unlinked Entries") # TODO translate self.setWindowModality(Qt.WindowModality.ApplicationModal) self.setMinimumSize(500, 400) self.root_layout = QVBoxLayout(self) @@ -40,7 +40,7 @@ def __init__(self, driver: "QtDriver", tracker: MissingRegistry): self.desc_widget.setWordWrap(True) self.desc_widget.setText(f""" Are you sure you want to delete the following {self.tracker.missing_files_count} entries? - """) + """) # TODO translate self.desc_widget.setAlignment(Qt.AlignmentFlag.AlignCenter) self.list_view = QListView() @@ -53,13 +53,13 @@ def __init__(self, driver: "QtDriver", tracker: MissingRegistry): self.button_layout.addStretch(1) self.cancel_button = QPushButton() - self.cancel_button.setText("&Cancel") + self.cancel_button.setText("&Cancel") # TODO translate self.cancel_button.setDefault(True) self.cancel_button.clicked.connect(self.hide) self.button_layout.addWidget(self.cancel_button) self.delete_button = QPushButton() - self.delete_button.setText("&Delete") + self.delete_button.setText("&Delete") # TODO translate self.delete_button.clicked.connect(self.hide) self.delete_button.clicked.connect(lambda: self.delete_entries()) self.button_layout.addWidget(self.delete_button) @@ -71,7 +71,7 @@ def __init__(self, driver: "QtDriver", tracker: MissingRegistry): def refresh_list(self): self.desc_widget.setText(f""" Are you sure you want to delete the following {self.tracker.missing_files_count} entries? - """) + """) # TODO translate self.model.clear() for i in self.tracker.missing_files: @@ -81,10 +81,10 @@ def refresh_list(self): def delete_entries(self): def displayed_text(x): - return f"Deleting {x}/{self.tracker.missing_files_count} Unlinked Entries" + return f"Deleting {x}/{self.tracker.missing_files_count} Unlinked Entries" # TODO translate pw = ProgressWidget( - window_title="Deleting Entries", + window_title="Deleting Entries", # TODO translate label_text="", cancel_button_text=None, minimum=0, diff --git a/tagstudio/src/qt/modals/drop_import.py b/tagstudio/src/qt/modals/drop_import.py index ec94a3e50..7f4d77e85 100644 --- a/tagstudio/src/qt/modals/drop_import.py +++ b/tagstudio/src/qt/modals/drop_import.py @@ -41,7 +41,7 @@ def __init__(self, driver: "QtDriver"): self.driver: QtDriver = driver # Widget ====================== - self.setWindowTitle("Conflicting File(s)") + self.setWindowTitle("Conflicting File(s)") # TODO translate self.setWindowModality(Qt.WindowModality.ApplicationModal) self.setMinimumSize(500, 400) self.root_layout = QVBoxLayout(self) @@ -50,7 +50,9 @@ def __init__(self, driver: "QtDriver"): self.desc_widget = QLabel() self.desc_widget.setObjectName("descriptionLabel") self.desc_widget.setWordWrap(True) - self.desc_widget.setText("The following files have filenames already exist in the library") + self.desc_widget.setText( + "The following files have filenames already exist in the library" + ) # TODO translate self.desc_widget.setAlignment(Qt.AlignmentFlag.AlignCenter) # Duplicate File List ======== @@ -65,25 +67,25 @@ def __init__(self, driver: "QtDriver"): self.button_layout.addStretch(1) self.skip_button = QPushButton() - self.skip_button.setText("&Skip") + self.skip_button.setText("&Skip") # TODO translate self.skip_button.setDefault(True) self.skip_button.clicked.connect(lambda: self.begin_transfer(DuplicateChoice.SKIP)) self.button_layout.addWidget(self.skip_button) self.overwrite_button = QPushButton() - self.overwrite_button.setText("&Overwrite") + self.overwrite_button.setText("&Overwrite") # TODO translate self.overwrite_button.clicked.connect( lambda: self.begin_transfer(DuplicateChoice.OVERWRITE) ) self.button_layout.addWidget(self.overwrite_button) self.rename_button = QPushButton() - self.rename_button.setText("&Rename") + self.rename_button.setText("&Rename") # TODO translate self.rename_button.clicked.connect(lambda: self.begin_transfer(DuplicateChoice.RENAME)) self.button_layout.addWidget(self.rename_button) self.cancel_button = QPushButton() - self.cancel_button.setText("&Cancel") + self.cancel_button.setText("&Cancel") # TODO translate self.cancel_button.clicked.connect(lambda: self.begin_transfer(DuplicateChoice.CANCEL)) self.button_layout.addWidget(self.cancel_button) @@ -138,7 +140,7 @@ def ask_duplicates_choice(self): """Display the message widgeth with a list of the duplicated files.""" self.desc_widget.setText( f"The following {len(self.duplicate_files)} file(s) have filenames already exist in the library." # noqa: E501 - ) + ) # TODO translate self.model.clear() for dupe in self.duplicate_files: @@ -158,17 +160,15 @@ def begin_transfer(self, choice: DuplicateChoice | None = None): return def displayed_text(x): - text = ( - f"Importing New Files...\n{x[0] + 1} File{'s' if x[0] + 1 != 1 else ''} Imported." - ) + text = f"Importing New Files...\n{x[0] + 1} File{'s' if x[0] + 1 != 1 else ''} Imported." # TODO translate if self.choice: text += f" {x[1]} {self.choice.value}" return text pw = ProgressWidget( - window_title="Import Files", - label_text="Importing New Files...", + window_title="Import Files", # TODO translate + label_text="Importing New Files...", # TODO translate cancel_button_text=None, minimum=0, maximum=len(self.files), diff --git a/tagstudio/src/qt/modals/file_extension.py b/tagstudio/src/qt/modals/file_extension.py index 224638ca0..19c6f06d1 100644 --- a/tagstudio/src/qt/modals/file_extension.py +++ b/tagstudio/src/qt/modals/file_extension.py @@ -35,7 +35,7 @@ def __init__(self, library: "Library"): super().__init__() # Initialize Modal ===================================================== self.lib = library - self.setWindowTitle("File Extensions") + self.setWindowTitle("File Extensions") # TODO translate self.setWindowModality(Qt.WindowModality.ApplicationModal) self.setMinimumSize(240, 400) self.root_layout = QVBoxLayout(self) @@ -50,7 +50,7 @@ def __init__(self, library: "Library"): # Create "Add Button" Widget ------------------------------------------- self.add_button = QPushButton() - self.add_button.setText("&Add Extension") + self.add_button.setText("&Add Extension") # TODO translate self.add_button.clicked.connect(self.add_item) self.add_button.setDefault(True) self.add_button.setMinimumWidth(100) @@ -61,11 +61,11 @@ def __init__(self, library: "Library"): self.mode_layout.setContentsMargins(0, 0, 0, 0) self.mode_layout.setSpacing(12) self.mode_label = QLabel() - self.mode_label.setText("List Mode:") + self.mode_label.setText("List Mode:") # TODO translate self.mode_combobox = QComboBox() self.mode_combobox.setEditable(False) - self.mode_combobox.addItem("Include") - self.mode_combobox.addItem("Exclude") + self.mode_combobox.addItem("Include") # TODO translate + self.mode_combobox.addItem("Exclude") # TODO translate is_exclude_list = int(bool(self.lib.prefs(LibraryPrefs.IS_EXCLUDE_LIST))) diff --git a/tagstudio/src/qt/modals/fix_dupes.py b/tagstudio/src/qt/modals/fix_dupes.py index fccdb5bcb..0476e644e 100644 --- a/tagstudio/src/qt/modals/fix_dupes.py +++ b/tagstudio/src/qt/modals/fix_dupes.py @@ -30,7 +30,7 @@ def __init__(self, library: "Library", driver: "QtDriver"): self.driver = driver self.count = -1 self.filename = "" - self.setWindowTitle("Fix Duplicate Files") + self.setWindowTitle("Fix Duplicate Files") # TODO translate self.setWindowModality(Qt.WindowModality.ApplicationModal) self.setMinimumSize(400, 300) self.root_layout = QVBoxLayout(self) @@ -44,7 +44,7 @@ def __init__(self, library: "Library", driver: "QtDriver"): self.desc_widget.setStyleSheet("text-align:left;") self.desc_widget.setText( "TagStudio supports importing DupeGuru results to manage duplicate files." - ) + ) # TODO translate self.desc_widget.setAlignment(Qt.AlignmentFlag.AlignCenter) self.dupe_count = QLabel() @@ -54,24 +54,24 @@ def __init__(self, library: "Library", driver: "QtDriver"): self.file_label = QLabel() self.file_label.setObjectName("fileLabel") - self.file_label.setText("No DupeGuru File Selected") + self.file_label.setText("No DupeGuru File Selected") # TODO translate self.open_button = QPushButton() - self.open_button.setText("&Load DupeGuru File") + self.open_button.setText("&Load DupeGuru File") # TODO translate self.open_button.clicked.connect(self.select_file) self.mirror_modal = MirrorEntriesModal(self.driver, self.tracker) self.mirror_modal.done.connect(self.refresh_dupes) self.mirror_button = QPushButton() - self.mirror_button.setText("&Mirror Entries") + self.mirror_button.setText("&Mirror Entries") # TODO translate self.mirror_button.clicked.connect(self.mirror_modal.show) self.mirror_desc = QLabel() self.mirror_desc.setWordWrap(True) self.mirror_desc.setText( "Mirror the Entry data across each duplicate match set, combining all data while not " "removing or duplicating fields. This operation will not delete any files or data." - ) + ) # TODO translate self.advice_label = QLabel() self.advice_label.setWordWrap(True) @@ -80,7 +80,7 @@ def __init__(self, library: "Library", driver: "QtDriver"): "After mirroring, you're free to use DupeGuru to delete the unwanted files. " "Afterwards, use TagStudio's \"Fix Unlinked Entries\" feature in the " "Tools menu in order to delete the unlinked Entries." - ) + )# TODO translate # fmt: on self.button_container = QWidget() @@ -89,7 +89,7 @@ def __init__(self, library: "Library", driver: "QtDriver"): self.button_layout.addStretch(1) self.done_button = QPushButton() - self.done_button.setText("&Done") + self.done_button.setText("&Done") # TODO translate self.done_button.setDefault(True) self.done_button.clicked.connect(self.hide) self.button_layout.addWidget(self.done_button) @@ -108,9 +108,11 @@ def __init__(self, library: "Library", driver: "QtDriver"): self.set_dupe_count(-1) def select_file(self): - qfd = QFileDialog(self, "Open DupeGuru Results File", str(self.lib.library_dir)) + qfd = QFileDialog( + self, "Open DupeGuru Results File", str(self.lib.library_dir) + ) # TODO translate qfd.setFileMode(QFileDialog.FileMode.ExistingFile) - qfd.setNameFilter("DupeGuru Files (*.dupeguru)") + qfd.setNameFilter("DupeGuru Files (*.dupeguru)") # TODO translate if qfd.exec_(): filename = qfd.selectedFiles() if filename: @@ -120,7 +122,7 @@ def set_filename(self, filename: str): if filename: self.file_label.setText(filename) else: - self.file_label.setText("No DupeGuru File Selected") + self.file_label.setText("No DupeGuru File Selected") # TODO translate self.filename = filename self.refresh_dupes() self.mirror_modal.refresh_list() @@ -132,10 +134,10 @@ def refresh_dupes(self): def set_dupe_count(self, count: int): if count < 0: self.mirror_button.setDisabled(True) - self.dupe_count.setText("Duplicate File Matches: N/A") + self.dupe_count.setText("Duplicate File Matches: N/A") # TODO translate elif count == 0: self.mirror_button.setDisabled(True) - self.dupe_count.setText(f"Duplicate File Matches: {count}") + self.dupe_count.setText(f"Duplicate File Matches: {count}") # TODO translate else: self.mirror_button.setDisabled(False) - self.dupe_count.setText(f"Duplicate File Matches: {count}") + self.dupe_count.setText(f"Duplicate File Matches: {count}") # TODO translate diff --git a/tagstudio/src/qt/modals/fix_unlinked.py b/tagstudio/src/qt/modals/fix_unlinked.py index 7c580d3f7..2afae7fb6 100644 --- a/tagstudio/src/qt/modals/fix_unlinked.py +++ b/tagstudio/src/qt/modals/fix_unlinked.py @@ -29,7 +29,7 @@ def __init__(self, library: "Library", driver: "QtDriver"): self.missing_count = -1 self.dupe_count = -1 - self.setWindowTitle("Fix Unlinked Entries") + self.setWindowTitle("Fix Unlinked Entries") # TODO translate self.setWindowModality(Qt.WindowModality.ApplicationModal) self.setMinimumSize(400, 300) self.root_layout = QVBoxLayout(self) @@ -45,7 +45,7 @@ def __init__(self, library: "Library", driver: "QtDriver"): "it is then considered unlinked.\n\n" "Unlinked entries may be automatically relinked via searching your directories, " "manually relinked by the user, or deleted if desired." - ) + ) # TODO translate self.missing_count_label = QLabel() self.missing_count_label.setObjectName("missingCountLabel") @@ -58,14 +58,14 @@ def __init__(self, library: "Library", driver: "QtDriver"): self.dupe_count_label.setAlignment(Qt.AlignmentFlag.AlignCenter) self.refresh_unlinked_button = QPushButton() - self.refresh_unlinked_button.setText("&Refresh All") + self.refresh_unlinked_button.setText("&Refresh All") # TODO translate self.refresh_unlinked_button.clicked.connect(self.refresh_missing_files) self.merge_class = MergeDuplicateEntries(self.lib, self.driver) self.relink_class = RelinkUnlinkedEntries(self.tracker) self.search_button = QPushButton() - self.search_button.setText("&Search && Relink") + self.search_button.setText("&Search && Relink") # TODO translate self.relink_class.done.connect( # refresh the grid lambda: ( @@ -76,7 +76,7 @@ def __init__(self, library: "Library", driver: "QtDriver"): self.search_button.clicked.connect(self.relink_class.repair_entries) self.manual_button = QPushButton() - self.manual_button.setText("&Manual Relink") + self.manual_button.setText("&Manual Relink") # TODO translate self.manual_button.setHidden(True) self.delete_button = QPushButton() @@ -88,7 +88,7 @@ def __init__(self, library: "Library", driver: "QtDriver"): self.driver.filter_items(), ) ) - self.delete_button.setText("De&lete Unlinked Entries") + self.delete_button.setText("De&lete Unlinked Entries") # TODO translate self.delete_button.clicked.connect(self.delete_modal.show) self.button_container = QWidget() @@ -97,7 +97,7 @@ def __init__(self, library: "Library", driver: "QtDriver"): self.button_layout.addStretch(1) self.done_button = QPushButton() - self.done_button.setText("&Done") + self.done_button.setText("&Done") # TODO translate self.done_button.setDefault(True) self.done_button.clicked.connect(self.hide) self.button_layout.addWidget(self.done_button) @@ -116,8 +116,8 @@ def __init__(self, library: "Library", driver: "QtDriver"): def refresh_missing_files(self): pw = ProgressWidget( - window_title="Scanning Library", - label_text="Scanning Library for Unlinked Entries...", + window_title="Scanning Library", # TODO translate + label_text="Scanning Library for Unlinked Entries...", # TODO translate cancel_button_text=None, minimum=0, maximum=self.lib.entries_count, @@ -139,9 +139,11 @@ def set_missing_count(self, count: int | None = None): if self.missing_count < 0: self.search_button.setDisabled(True) self.delete_button.setDisabled(True) - self.missing_count_label.setText("Unlinked Entries: N/A") + self.missing_count_label.setText("Unlinked Entries: N/A") # TODO translate else: # disable buttons if there are no files to fix self.search_button.setDisabled(self.missing_count == 0) self.delete_button.setDisabled(self.missing_count == 0) - self.missing_count_label.setText(f"Unlinked Entries: {self.missing_count}") + self.missing_count_label.setText( + f"Unlinked Entries: {self.missing_count}" + ) # TODO translate diff --git a/tagstudio/src/qt/modals/folders_to_tags.py b/tagstudio/src/qt/modals/folders_to_tags.py index f9962a0e5..ca2d48ccb 100644 --- a/tagstudio/src/qt/modals/folders_to_tags.py +++ b/tagstudio/src/qt/modals/folders_to_tags.py @@ -164,7 +164,7 @@ def __init__(self, library: "Library", driver: "QtDriver"): self.count = -1 self.filename = "" - self.setWindowTitle("Create Tags From Folders") + self.setWindowTitle("Create Tags From Folders") # TODO translate self.setWindowModality(Qt.WindowModality.ApplicationModal) self.setMinimumSize(640, 640) self.root_layout = QVBoxLayout(self) @@ -174,7 +174,7 @@ def __init__(self, library: "Library", driver: "QtDriver"): self.title_widget.setObjectName("title") self.title_widget.setWordWrap(True) self.title_widget.setStyleSheet("font-weight:bold;" "font-size:14px;" "padding-top: 6px") - self.title_widget.setText("Create Tags From Folders") + self.title_widget.setText("Create Tags From Folders") # TODO translate self.title_widget.setAlignment(Qt.AlignmentFlag.AlignCenter) self.desc_widget = QLabel() @@ -190,10 +190,10 @@ def __init__(self, library: "Library", driver: "QtDriver"): self.open_close_button_layout = QHBoxLayout(self.open_close_button_w) self.open_all_button = QPushButton() - self.open_all_button.setText("Open All") + self.open_all_button.setText("Open All") # TODO translate self.open_all_button.clicked.connect(lambda: self.set_all_branches(False)) self.close_all_button = QPushButton() - self.close_all_button.setText("Close All") + self.close_all_button.setText("Close All") # TODO translate self.close_all_button.clicked.connect(lambda: self.set_all_branches(True)) self.open_close_button_layout.addWidget(self.open_all_button) @@ -212,7 +212,7 @@ def __init__(self, library: "Library", driver: "QtDriver"): self.scroll_area.setWidget(self.scroll_contents) self.apply_button = QPushButton() - self.apply_button.setText("&Apply") + self.apply_button.setText("&Apply") # TODO translate self.apply_button.setMinimumWidth(100) self.apply_button.clicked.connect(self.on_apply) diff --git a/tagstudio/src/qt/modals/merge_dupe_entries.py b/tagstudio/src/qt/modals/merge_dupe_entries.py index 66c94dda7..14891d47a 100644 --- a/tagstudio/src/qt/modals/merge_dupe_entries.py +++ b/tagstudio/src/qt/modals/merge_dupe_entries.py @@ -25,8 +25,8 @@ def __init__(self, library: "Library", driver: "QtDriver"): def merge_entries(self): pw = ProgressWidget( - window_title="Merging Duplicate Entries", - label_text="Merging Duplicate Entries...", + window_title="Merging Duplicate Entries", # TODO translate + label_text="Merging Duplicate Entries...", # TODO translate cancel_button_text=None, minimum=0, maximum=self.tracker.groups_count, diff --git a/tagstudio/src/qt/modals/mirror_entities.py b/tagstudio/src/qt/modals/mirror_entities.py index 6ab199570..4c0cd4aa4 100644 --- a/tagstudio/src/qt/modals/mirror_entities.py +++ b/tagstudio/src/qt/modals/mirror_entities.py @@ -30,7 +30,7 @@ class MirrorEntriesModal(QWidget): def __init__(self, driver: "QtDriver", tracker: DupeRegistry): super().__init__() self.driver = driver - self.setWindowTitle("Mirror Entries") + self.setWindowTitle("Mirror Entries") # TODO translate self.setWindowModality(Qt.WindowModality.ApplicationModal) self.setMinimumSize(500, 400) self.root_layout = QVBoxLayout(self) @@ -43,7 +43,7 @@ def __init__(self, driver: "QtDriver", tracker: DupeRegistry): self.desc_widget.setText(f""" Are you sure you want to mirror the following {self.tracker.groups_count} Entries? - """) + """) # TODO translate self.desc_widget.setAlignment(Qt.AlignmentFlag.AlignCenter) self.list_view = QListView() @@ -56,13 +56,13 @@ def __init__(self, driver: "QtDriver", tracker: DupeRegistry): self.button_layout.addStretch(1) self.cancel_button = QPushButton() - self.cancel_button.setText("&Cancel") + self.cancel_button.setText("&Cancel") # TODO translate self.cancel_button.setDefault(True) self.cancel_button.clicked.connect(self.hide) self.button_layout.addWidget(self.cancel_button) self.mirror_button = QPushButton() - self.mirror_button.setText("&Mirror") + self.mirror_button.setText("&Mirror") # TODO translate self.mirror_button.clicked.connect(self.hide) self.mirror_button.clicked.connect(self.mirror_entries) self.button_layout.addWidget(self.mirror_button) @@ -82,10 +82,10 @@ def refresh_list(self): def mirror_entries(self): def displayed_text(x): - return f"Mirroring {x + 1}/{self.tracker.groups_count} Entries..." + return f"Mirroring {x + 1}/{self.tracker.groups_count} Entries..." # TODO translate pw = ProgressWidget( - window_title="Mirroring Entries", + window_title="Mirroring Entries", # TODO translate label_text="", cancel_button_text=None, minimum=0, diff --git a/tagstudio/src/qt/modals/relink_unlinked.py b/tagstudio/src/qt/modals/relink_unlinked.py index ab2a1d408..87d67a6c0 100644 --- a/tagstudio/src/qt/modals/relink_unlinked.py +++ b/tagstudio/src/qt/modals/relink_unlinked.py @@ -17,12 +17,12 @@ def __init__(self, tracker: MissingRegistry): def repair_entries(self): def displayed_text(x): - text = f"Attempting to Relink {x}/{self.tracker.missing_files_count} Entries. \n" - text += f"{self.tracker.files_fixed_count} Successfully Relinked." + text = f"Attempting to Relink {x}/{self.tracker.missing_files_count} Entries. \n" # TODO translate + text += f"{self.tracker.files_fixed_count} Successfully Relinked." # TODO translate return text pw = ProgressWidget( - window_title="Relinking Entries", + window_title="Relinking Entries", # TODO translate label_text="", cancel_button_text=None, minimum=0, diff --git a/tagstudio/src/qt/modals/tag_database.py b/tagstudio/src/qt/modals/tag_database.py index 168420d52..15f6289e5 100644 --- a/tagstudio/src/qt/modals/tag_database.py +++ b/tagstudio/src/qt/modals/tag_database.py @@ -44,7 +44,7 @@ def __init__(self, library: Library): self.search_field = QLineEdit() self.search_field.setObjectName("searchField") self.search_field.setMinimumSize(QSize(0, 32)) - self.search_field.setPlaceholderText("Search Tags") + self.search_field.setPlaceholderText("Search Tags") # TODO translate self.search_field.textEdited.connect(lambda: self.update_tags(self.search_field.text())) self.search_field.returnPressed.connect( lambda checked=False: self.on_return(self.search_field.text()) @@ -63,7 +63,7 @@ def __init__(self, library: Library): self.scroll_area.setWidget(self.scroll_contents) self.create_tag_button = QPushButton() - self.create_tag_button.setText("Create Tag") + self.create_tag_button.setText("Create Tag") # TODO translate self.create_tag_button.clicked.connect(self.build_tag) self.root_layout.addWidget(self.search_field) @@ -74,8 +74,8 @@ def __init__(self, library: Library): def build_tag(self): self.modal = PanelModal( BuildTagPanel(self.lib), - "New Tag", - "Add Tag", + "New Tag", # TODO translate + "Add Tag", # TODO translate has_save=True, ) @@ -134,8 +134,10 @@ def remove_tag(self, tag: Tag): return message_box = QMessageBox() - message_box.setWindowTitle("Remove Tag") - message_box.setText(f'Are you sure you want to delete the tag "{tag.name}"?') + message_box.setWindowTitle("Remove Tag") # TODO translate + message_box.setText( + f'Are you sure you want to delete the tag "{tag.name}"?' + ) # TODO translate message_box.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel) # type: ignore message_box.setIcon(QMessageBox.Question) # type: ignore @@ -156,7 +158,7 @@ def edit_tag(self, tag: Tag): self.edit_modal = PanelModal( build_tag_panel, tag.name, - "Edit Tag", + "Edit Tag", # TODO translate done_callback=(self.update_tags(self.search_field.text())), has_save=True, ) diff --git a/tagstudio/src/qt/modals/tag_search.py b/tagstudio/src/qt/modals/tag_search.py index 72f3650b9..2189b3164 100644 --- a/tagstudio/src/qt/modals/tag_search.py +++ b/tagstudio/src/qt/modals/tag_search.py @@ -41,7 +41,7 @@ def __init__(self, library: Library, exclude: list[int] | None = None): self.search_field = QLineEdit() self.search_field.setObjectName("searchField") self.search_field.setMinimumSize(QSize(0, 32)) - self.search_field.setPlaceholderText("Search Tags") + self.search_field.setPlaceholderText("Search Tags") # TODO translate self.search_field.textEdited.connect(lambda: self.update_tags(self.search_field.text())) self.search_field.returnPressed.connect( lambda checked=False: self.on_return(self.search_field.text()) diff --git a/tagstudio/src/qt/platform_strings.py b/tagstudio/src/qt/platform_strings.py index 9eda3ef8b..7131da897 100644 --- a/tagstudio/src/qt/platform_strings.py +++ b/tagstudio/src/qt/platform_strings.py @@ -11,6 +11,6 @@ class PlatformStrings: open_file_str: str = "Open in file explorer" if platform.system() == "Windows": - open_file_str = "Open in Explorer" + open_file_str = "Open in Explorer" # TODO translate elif platform.system() == "Darwin": - open_file_str = "Reveal in Finder" + open_file_str = "Reveal in Finder" # TODO translate diff --git a/tagstudio/src/qt/ui/home_ui.py b/tagstudio/src/qt/ui/home_ui.py index 0d986f6ad..3474f9573 100644 --- a/tagstudio/src/qt/ui/home_ui.py +++ b/tagstudio/src/qt/ui/home_ui.py @@ -147,14 +147,14 @@ def setupUi(self, MainWindow): def retranslateUi(self, MainWindow): MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"MainWindow", None)) - self.comboBox_2.setItemText(0, QCoreApplication.translate("MainWindow", u"And (includes all tags)", None)) - self.comboBox_2.setItemText(1, QCoreApplication.translate("MainWindow", u"Or (includes any tag)", None)) + self.comboBox_2.setItemText(0, QCoreApplication.translate("MainWindow", u"And (includes all tags)", None)) # TODO remove? + self.comboBox_2.setItemText(1, QCoreApplication.translate("MainWindow", u"Or (includes any tag)", None)) # TODO remove? self.comboBox.setCurrentText("") - self.comboBox.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Thumbnail Size", None)) + self.comboBox.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Thumbnail Size", None)) # TODO translate? self.backButton.setText(QCoreApplication.translate("MainWindow", u"<", None)) self.forwardButton.setText(QCoreApplication.translate("MainWindow", u">", None)) - self.searchField.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Search Entries", None)) + self.searchField.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Search Entries", None)) # TODO translate? self.searchButton.setText(QCoreApplication.translate("MainWindow", u"Search", None)) # retranslateUi diff --git a/tagstudio/src/qt/widgets/item_thumb.py b/tagstudio/src/qt/widgets/item_thumb.py index 9935fe4e7..48ea0dbd7 100644 --- a/tagstudio/src/qt/widgets/item_thumb.py +++ b/tagstudio/src/qt/widgets/item_thumb.py @@ -217,7 +217,7 @@ def __init__( self.thumb_button.setContextMenuPolicy(Qt.ContextMenuPolicy.ActionsContextMenu) self.opener = FileOpenerHelper("") - open_file_action = QAction("Open file", self) + open_file_action = QAction("Open file", self) # TODO translate open_file_action.triggered.connect(self.opener.open_file) open_explorer_action = QAction(PlatformStrings.open_file_str, self) open_explorer_action.triggered.connect(self.opener.open_explorer) @@ -306,7 +306,7 @@ def __init__( self.cb_layout.addWidget(badge) # Filename Label ======================================================= - self.file_label = QLabel(text="Filename") + self.file_label = QLabel(text="Filename") # TODO translate self.file_label.setStyleSheet(ItemThumb.filename_style) self.file_label.setMaximumHeight(self.label_height) if not show_filename_label: diff --git a/tagstudio/src/qt/widgets/landing.py b/tagstudio/src/qt/widgets/landing.py index c1a5a7d8c..935268ef1 100644 --- a/tagstudio/src/qt/widgets/landing.py +++ b/tagstudio/src/qt/widgets/landing.py @@ -62,7 +62,7 @@ def __init__(self, driver: "QtDriver", pixel_ratio: float): open_shortcut_text = "(Ctrl+O)" self.open_button: QPushButton = QPushButton() self.open_button.setMinimumWidth(200) - self.open_button.setText(f"Open/Create Library {open_shortcut_text}") + self.open_button.setText(f"Open/Create Library {open_shortcut_text}") # TODO translate self.open_button.clicked.connect(self.driver.open_library_from_dialog) # Create status label -------------------------------------------------- diff --git a/tagstudio/src/qt/widgets/migration_modal.py b/tagstudio/src/qt/widgets/migration_modal.py index a2978f562..2bf13a95a 100644 --- a/tagstudio/src/qt/widgets/migration_modal.py +++ b/tagstudio/src/qt/widgets/migration_modal.py @@ -54,7 +54,7 @@ def __init__(self, path: Path): self.is_migration_initialized: bool = False self.discrepancies: list[str] = [] - self.title: str = f'Save Format Migration: "{self.path}"' + self.title: str = f'Save Format Migration: "{self.path}"' # TODO translate self.warning: str = "(!)" self.old_entry_count: int = 0 @@ -77,7 +77,7 @@ def __init__(self, path: Path): def init_page_info(self) -> None: """Initialize the migration info page.""" body_wrapper: PagedBodyWrapper = PagedBodyWrapper() - body_label: QLabel = QLabel( + body_label: QLabel = QLabel( # TODO translate "Library save files created with TagStudio versions 9.4 and below will " "need to be migrated to the new v9.5+ format." "
" @@ -93,8 +93,8 @@ def init_page_info(self) -> None: body_wrapper.layout().addWidget(body_label) body_wrapper.layout().setContentsMargins(0, 36, 0, 0) - cancel_button: QPushButtonWrapper = QPushButtonWrapper("Cancel") - next_button: QPushButtonWrapper = QPushButtonWrapper("Continue") + cancel_button: QPushButtonWrapper = QPushButtonWrapper("Cancel") # TODO translate + next_button: QPushButtonWrapper = QPushButtonWrapper("Continue") # TODO translate cancel_button.clicked.connect(self.migration_cancelled.emit) self.stack.append( @@ -115,18 +115,18 @@ def init_page_convert(self) -> None: body_container_layout.setContentsMargins(0, 0, 0, 0) tab: str = " " - self.match_text: str = "Matched" - self.differ_text: str = "Discrepancy" - - entries_text: str = "Entries:" - tags_text: str = "Tags:" - shorthand_text: str = tab + "Shorthands:" - subtags_text: str = tab + "Parent Tags:" - aliases_text: str = tab + "Aliases:" - colors_text: str = tab + "Colors:" - ext_text: str = "File Extension List:" - ext_type_text: str = "Extension List Type:" - desc_text: str = ( + self.match_text: str = "Matched" # TODO translate + self.differ_text: str = "Discrepancy" # TODO translate + + entries_text: str = "Entries:" # TODO translate + tags_text: str = "Tags:" # TODO translate + shorthand_text: str = tab + "Shorthands:" # TODO translate + subtags_text: str = tab + "Parent Tags:" # TODO translate + aliases_text: str = tab + "Aliases:" # TODO translate + colors_text: str = tab + "Colors:" # TODO translate + ext_text: str = "File Extension List:" # TODO translate + ext_type_text: str = "Extension List Type:" # TODO translate + desc_text: str = ( # TODO translate "
Start and preview the results of the library migration process. " 'The converted library will not be used unless you click "Finish Migration". ' "

" @@ -137,8 +137,8 @@ def init_page_convert(self) -> None: "This process may take up to several minutes for larger libraries." "" ) - path_parity_text: str = tab + "Paths:" - field_parity_text: str = tab + "Fields:" + path_parity_text: str = tab + "Paths:" # TODO translate + field_parity_text: str = tab + "Fields:" # TODO translate self.entries_row: int = 0 self.path_row: int = 1 @@ -153,7 +153,7 @@ def init_page_convert(self) -> None: old_lib_container: QWidget = QWidget() old_lib_layout: QVBoxLayout = QVBoxLayout(old_lib_container) - old_lib_title: QLabel = QLabel("

v9.4 Library

") + old_lib_title: QLabel = QLabel("

v9.4 Library

") # TODO translate old_lib_title.setAlignment(Qt.AlignmentFlag.AlignCenter) old_lib_layout.addWidget(old_lib_title) @@ -215,7 +215,7 @@ def init_page_convert(self) -> None: new_lib_container: QWidget = QWidget() new_lib_layout: QVBoxLayout = QVBoxLayout(new_lib_container) - new_lib_title: QLabel = QLabel("

v9.5+ Library

") + new_lib_title: QLabel = QLabel("

v9.5+ Library

") # TODO translate new_lib_title.setAlignment(Qt.AlignmentFlag.AlignCenter) new_lib_layout.addWidget(new_lib_title) @@ -291,13 +291,13 @@ def init_page_convert(self) -> None: self.body_wrapper_01.layout().addWidget(desc_label) self.body_wrapper_01.layout().setSpacing(12) - back_button: QPushButtonWrapper = QPushButtonWrapper("Back") - start_button: QPushButtonWrapper = QPushButtonWrapper("Start and Preview") + back_button: QPushButtonWrapper = QPushButtonWrapper("Back") # TODO translate + start_button: QPushButtonWrapper = QPushButtonWrapper("Start and Preview") # TODO translate start_button.setMinimumWidth(120) start_button.clicked.connect(self.migrate) start_button.clicked.connect(lambda: finish_button.setDisabled(False)) start_button.clicked.connect(lambda: start_button.setDisabled(True)) - finish_button: QPushButtonWrapper = QPushButtonWrapper("Finish Migration") + finish_button: QPushButtonWrapper = QPushButtonWrapper("Finish Migration") # TODO translate finish_button.setMinimumWidth(120) finish_button.setDisabled(True) finish_button.clicked.connect(self.finish_migration) @@ -348,9 +348,9 @@ def migration_progress(self, skip_ui: bool = False): lambda x: ( pb.setLabelText(f"

{x}

"), self.update_sql_value_ui(show_msg_box=False) - if x == "Checking for Parity..." + if x == "Checking for Parity..." # TODO translate else (), - self.update_parity_ui() if x == "Checking for Parity..." else (), + self.update_parity_ui() if x == "Checking for Parity..." else (), # TODO translate ) ) r = CustomRunnable(iterator.run) @@ -367,21 +367,23 @@ def migration_iterator(self): """Iterate over the library migration process.""" try: # Convert JSON Library to SQLite - yield "Creating SQL Database Tables..." + yield "Creating SQL Database Tables..." # TODO translate self.sql_lib = SqliteLibrary() self.temp_path: Path = ( self.json_lib.library_dir / TS_FOLDER_NAME / "migration_ts_library.sqlite" ) self.sql_lib.storage_path = self.temp_path if self.temp_path.exists(): - logger.info('Temporary migration file "temp_path" already exists. Removing...') + logger.info( + 'Temporary migration file "temp_path" already exists. Removing...' + ) # TODO translate self.temp_path.unlink() self.sql_lib.open_sqlite_library( self.json_lib.library_dir, is_new=True, add_default_data=False ) - yield f"Migrating {len(self.json_lib.entries):,d} File Entries..." + yield f"Migrating {len(self.json_lib.entries):,d} File Entries..." # TODO translate self.sql_lib.migrate_json_to_sqlite(self.json_lib) - yield "Checking for Parity..." + yield "Checking for Parity..." # TODO translate check_set = set() check_set.add(self.check_field_parity()) check_set.add(self.check_path_parity()) @@ -391,9 +393,9 @@ def migration_iterator(self): check_set.add(self.check_color_parity()) self.update_parity_ui() if False not in check_set: - yield "Migration Complete!" + yield "Migration Complete!" # TODO translate else: - yield "Migration Complete, Discrepancies Found" + yield "Migration Complete, Discrepancies Found" # TODO translate self.done = True except Exception as e: @@ -432,16 +434,16 @@ def update_sql_value_ui(self, show_msg_box: bool = True): self.sql_lib.prefs(LibraryPrefs.IS_EXCLUDE_LIST), self.old_ext_type, ) - logger.info("Parity check complete!") + logger.info("Parity check complete!") # TODO translate if self.discrepancies: - logger.warning("Discrepancies found:") + logger.warning("Discrepancies found:") # TODO translate logger.warning("\n".join(self.discrepancies)) QApplication.beep() if not show_msg_box: return msg_box = QMessageBox() - msg_box.setWindowTitle("Library Discrepancies Found") - msg_box.setText( + msg_box.setWindowTitle("Library Discrepancies Found") # TODO translate + msg_box.setText( # TODO translate "Discrepancies were found between the original and converted library formats. " "Please review and choose to whether continue with the migration or to cancel." ) @@ -546,7 +548,7 @@ def sanitize_json_field(value): "[Field Comparison]", message=f"NEW (SQL): SQL Entry ID mismatch: {json_entry.id+1}", ) - self.discrepancies.append( + self.discrepancies.append( # TODO translate f"[Field Comparison]:\nNEW (SQL): SQL Entry ID not found: {json_entry.id+1}" ) self.field_parity = False @@ -624,7 +626,7 @@ def sanitize_json_field(value): and sql_fields is not None and (json_fields == sql_fields) ): - self.discrepancies.append( + self.discrepancies.append( # TODO translate f"[Field Comparison]:\nOLD (JSON):{json_fields}\nNEW (SQL):{sql_fields}" ) self.field_parity = False @@ -676,7 +678,7 @@ def check_subtag_parity(self) -> bool: and json_subtags is not None and (sql_subtags == json_subtags) ): - self.discrepancies.append( + self.discrepancies.append( # TODO translate f"[Subtag Parity]:\nOLD (JSON):{json_subtags}\nNEW (SQL):{sql_subtags}" ) self.subtag_parity = False @@ -712,7 +714,7 @@ def check_alias_parity(self) -> bool: and json_aliases is not None and (sql_aliases == json_aliases) ): - self.discrepancies.append( + self.discrepancies.append( # TODO translate f"[Alias Parity]:\nOLD (JSON):{json_aliases}\nNEW (SQL):{sql_aliases}" ) self.alias_parity = False @@ -743,7 +745,7 @@ def check_shorthand_parity(self) -> bool: and json_shorthand is not None and (sql_shorthand == json_shorthand) ): - self.discrepancies.append( + self.discrepancies.append( # TODO translate f"[Shorthand Parity]:\nOLD (JSON):{json_shorthand}\nNEW (SQL):{sql_shorthand}" ) self.shorthand_parity = False @@ -774,7 +776,7 @@ def check_color_parity(self) -> bool: ) if not (sql_color is not None and json_color is not None and (sql_color == json_color)): - self.discrepancies.append( + self.discrepancies.append( # TODO translate f"[Color Parity]:\nOLD (JSON):{json_color}\nNEW (SQL):{sql_color}" ) self.color_parity = False diff --git a/tagstudio/src/qt/widgets/panel.py b/tagstudio/src/qt/widgets/panel.py index d18efb347..59e2234c8 100755 --- a/tagstudio/src/qt/widgets/panel.py +++ b/tagstudio/src/qt/widgets/panel.py @@ -49,7 +49,7 @@ def __init__( if not (save_callback or has_save): self.done_button = QPushButton() - self.done_button.setText("Done") + self.done_button.setText("Done") # TODO translate self.done_button.setAutoDefault(True) self.done_button.clicked.connect(self.hide) if done_callback: @@ -59,7 +59,7 @@ def __init__( if save_callback or has_save: self.cancel_button = QPushButton() - self.cancel_button.setText("Cancel") + self.cancel_button.setText("Cancel") # TODO translate self.cancel_button.clicked.connect(self.hide) self.cancel_button.clicked.connect(widget.reset) # self.cancel_button.clicked.connect(cancel_callback) @@ -67,7 +67,7 @@ def __init__( self.button_layout.addWidget(self.cancel_button) self.save_button = QPushButton() - self.save_button.setText("Save") + self.save_button.setText("Save") # TODO translate self.save_button.setAutoDefault(True) self.save_button.clicked.connect(self.hide) self.save_button.clicked.connect(self.saved.emit) diff --git a/tagstudio/src/qt/widgets/preview_panel.py b/tagstudio/src/qt/widgets/preview_panel.py index 7e8e0c8b3..50ed08424 100644 --- a/tagstudio/src/qt/widgets/preview_panel.py +++ b/tagstudio/src/qt/widgets/preview_panel.py @@ -119,7 +119,7 @@ def __init__(self, library: Library, driver: "QtDriver"): ) date_style = "font-size:12px;" - self.open_file_action = QAction("Open file", self) + self.open_file_action = QAction("Open file", self) # TODO translate self.open_explorer_action = QAction(PlatformStrings.open_file_str, self) self.preview_img = QPushButtonWrapper() @@ -279,7 +279,7 @@ def __init__(self, library: Library, driver: "QtDriver"): self.add_field_button.setCursor(Qt.CursorShape.PointingHandCursor) self.add_field_button.setMinimumSize(96, 28) self.add_field_button.setMaximumSize(96, 28) - self.add_field_button.setText("Add Field") + self.add_field_button.setText("Add Field") # TODO translate self.afb_layout.addWidget(self.add_field_button) self.add_field_modal = AddFieldModal(self.lib) self.place_add_field_button() @@ -303,7 +303,7 @@ def update_selected_entry(self, driver: "QtDriver"): self.driver.frame_content[grid_idx] = result def remove_field_prompt(self, name: str) -> str: - return f'Are you sure you want to remove field "{name}"?' + return f'Are you sure you want to remove field "{name}"?' # TODO translate def fill_libs_widget(self, layout: QVBoxLayout): settings = self.driver.settings @@ -341,7 +341,7 @@ def clear_layout(layout_item: QVBoxLayout): # remove any potential previous items clear_layout(layout) - label = QLabel("Recent Libraries") + label = QLabel("Recent Libraries") # TODO translate label.setAlignment(Qt.AlignmentFlag.AlignCenter) row_layout = QHBoxLayout() @@ -379,7 +379,7 @@ def set_button_style( lib = Path(full_val) if not lib.exists() or not (lib / TS_FOLDER_NAME).exists(): button.setDisabled(True) - button.setToolTip("Location is missing") + button.setToolTip("Location is missing") # TODO translate def open_library_button_clicked(path): return lambda: self.driver.open_library(Path(path)) @@ -492,16 +492,16 @@ def update_date_label(self, filepath: Path | None = None) -> None: created = dt.fromtimestamp(filepath.stat().st_ctime) modified: dt = dt.fromtimestamp(filepath.stat().st_mtime) self.date_created_label.setText( - f"Date Created: {dt.strftime(created, "%a, %x, %X")}" + f"Date Created: {dt.strftime(created, "%a, %x, %X")}" # TODO translate ) self.date_modified_label.setText( - f"Date Modified: {dt.strftime(modified, "%a, %x, %X")}" + f"Date Modified: {dt.strftime(modified, "%a, %x, %X")}" # TODO translate ) self.date_created_label.setHidden(False) self.date_modified_label.setHidden(False) elif filepath: - self.date_created_label.setText("Date Created: N/A") - self.date_modified_label.setText("Date Modified: N/A") + self.date_created_label.setText("Date Created: N/A") # TODO translate + self.date_modified_label.setText("Date Modified: N/A") # TODO translate self.date_created_label.setHidden(False) self.date_modified_label.setHidden(False) else: @@ -520,7 +520,7 @@ def update_widgets(self) -> bool: if not self.driver.selected: if self.selected or not self.initialized: - self.file_label.setText("No Items Selected") + self.file_label.setText("No Items Selected") # TODO translate self.file_label.set_file_path("") self.file_label.setCursor(Qt.CursorShape.ArrowCursor) @@ -777,7 +777,9 @@ def update_widgets(self) -> bool: self.media_player.hide() self.update_date_label() if self.selected != self.driver.selected: - self.file_label.setText(f"{len(self.driver.selected)} Items Selected") + self.file_label.setText( + f"{len(self.driver.selected)} Items Selected" + ) # TODO translate self.file_label.setCursor(Qt.CursorShape.ArrowCursor) self.file_label.set_file_path("") self.dimensions_label.setText("") @@ -872,10 +874,11 @@ def write_container(self, index: int, field: BaseField, is_mixed: bool = False): else: container = self.containers[index] + # TODO this is in severe need of refactoring due to exessive code duplication if isinstance(field, TagBoxField): container.set_title(field.type.name) container.set_inline(False) - title = f"{field.type.name} (Tag Box)" + title = f"{field.type.name} (Tag Box)" # TODO translate if not is_mixed: inner_container = container.get_inner_widget() @@ -916,8 +919,8 @@ def write_container(self, index: int, field: BaseField, is_mixed: bool = False): ) ) else: - text = "Mixed Data" - title = f"{field.type.name} (Wacky Tag Box)" + text = "Mixed Data" # TODO translate + title = f"{field.type.name} (Wacky Tag Box)" # TODO translate inner_container = TextWidget(title, text) container.set_inner_widget(inner_container) @@ -932,7 +935,7 @@ def write_container(self, index: int, field: BaseField, is_mixed: bool = False): assert isinstance(field.value, (str, type(None))) text = field.value or "" else: - text = "Mixed Data" + text = "Mixed Data" # TODO translate title = f"{field.type.name} ({field.type.type.value})" inner_container = TextWidget(title, text) @@ -941,7 +944,7 @@ def write_container(self, index: int, field: BaseField, is_mixed: bool = False): modal = PanelModal( EditTextLine(field.value), title=title, - window_title=f"Edit {field.type.type.value}", + window_title=f"Edit {field.type.type.value}", # TODO translate save_callback=( lambda content: ( self.update_field(field, content), @@ -973,15 +976,15 @@ def write_container(self, index: int, field: BaseField, is_mixed: bool = False): assert isinstance(field.value, (str, type(None))) text = (field.value or "").replace("\r", "\n") else: - text = "Mixed Data" - title = f"{field.type.name} (Text Box)" + text = "Mixed Data" # TODO translate + title = f"{field.type.name} (Text Box)" # TODO translate inner_container = TextWidget(title, text) container.set_inner_widget(inner_container) if not is_mixed: modal = PanelModal( EditTextBox(field.value), title=title, - window_title=f"Edit {field.type.name}", + window_title=f"Edit {field.type.name}", # TODO translate save_callback=( lambda content: ( self.update_field(field, content), @@ -1008,14 +1011,14 @@ def write_container(self, index: int, field: BaseField, is_mixed: bool = False): container.set_inline(False) # TODO: Localize this and/or add preferences. date = dt.strptime(field.value, "%Y-%m-%d %H:%M:%S") - title = f"{field.type.name} (Date)" + title = f"{field.type.name} (Date)" # TODO translate inner_container = TextWidget(title, date.strftime("%D - %r")) container.set_inner_widget(inner_container) except Exception: container.set_title(field.type.name) # container.set_editable(False) container.set_inline(False) - title = f"{field.type.name} (Date) (Unknown Format)" + title = f"{field.type.name} (Date) (Unknown Format)" # TODO translate inner_container = TextWidget(title, str(field.value)) container.set_inner_widget(inner_container) @@ -1029,15 +1032,15 @@ def write_container(self, index: int, field: BaseField, is_mixed: bool = False): ) ) else: - text = "Mixed Data" - title = f"{field.type.name} (Wacky Date)" + text = "Mixed Data" # TODO translate + title = f"{field.type.name} (Wacky Date)" # TODO translate inner_container = TextWidget(title, text) container.set_inner_widget(inner_container) else: logger.warning("write_container - unknown field", field=field) container.set_title(field.type.name) container.set_inline(False) - title = f"{field.type.name} (Unknown Field Type)" + title = f"{field.type.name} (Unknown Field Type)" # TODO translate inner_container = TextWidget(title, field.type.name) container.set_inner_widget(inner_container) container.set_remove_callback( @@ -1090,10 +1093,12 @@ def update_field(self, field: BaseField, content: str) -> None: def remove_message_box(self, prompt: str, callback: Callable) -> None: remove_mb = QMessageBox() remove_mb.setText(prompt) - remove_mb.setWindowTitle("Remove Field") + remove_mb.setWindowTitle("Remove Field") # TODO translate remove_mb.setIcon(QMessageBox.Icon.Warning) - cancel_button = remove_mb.addButton("&Cancel", QMessageBox.ButtonRole.DestructiveRole) - remove_mb.addButton("&Remove", QMessageBox.ButtonRole.RejectRole) + cancel_button = remove_mb.addButton( + "&Cancel", QMessageBox.ButtonRole.DestructiveRole + ) # TODO translate + remove_mb.addButton("&Remove", QMessageBox.ButtonRole.RejectRole) # TODO translate # remove_mb.setStandardButtons(QMessageBox.StandardButton.Cancel) remove_mb.setDefaultButton(cancel_button) remove_mb.setEscapeButton(cancel_button) diff --git a/tagstudio/src/qt/widgets/tag.py b/tagstudio/src/qt/widgets/tag.py index 2d4cc7ce1..21bb4f6d7 100644 --- a/tagstudio/src/qt/widgets/tag.py +++ b/tagstudio/src/qt/widgets/tag.py @@ -126,17 +126,17 @@ def __init__( self.bg_button.setFlat(True) self.bg_button.setText(tag.name) if has_edit: - edit_action = QAction("Edit", self) + edit_action = QAction("Edit", self) # TODO translate edit_action.triggered.connect(on_edit_callback) edit_action.triggered.connect(self.on_edit.emit) self.bg_button.addAction(edit_action) # if on_click_callback: self.bg_button.setContextMenuPolicy(Qt.ContextMenuPolicy.ActionsContextMenu) - search_for_tag_action = QAction("Search for Tag", self) + search_for_tag_action = QAction("Search for Tag", self) # TODO translate search_for_tag_action.triggered.connect(self.on_click.emit) self.bg_button.addAction(search_for_tag_action) - add_to_search_action = QAction("Add to Search", self) + add_to_search_action = QAction("Add to Search", self) # TODO translate self.bg_button.addAction(add_to_search_action) self.inner_layout = QHBoxLayout() diff --git a/tagstudio/src/qt/widgets/tag_box.py b/tagstudio/src/qt/widgets/tag_box.py index a26ec33af..5a59b25a4 100755 --- a/tagstudio/src/qt/widgets/tag_box.py +++ b/tagstudio/src/qt/widgets/tag_box.py @@ -75,7 +75,7 @@ def __init__( ) tsp = TagSearchPanel(self.driver.lib) tsp.tag_chosen.connect(lambda x: self.add_tag_callback(x)) - self.add_modal = PanelModal(tsp, title, "Add Tags") + self.add_modal = PanelModal(tsp, title, "Add Tags") # TODO translate self.add_button.clicked.connect( lambda: ( tsp.update_tags(), @@ -131,7 +131,7 @@ def edit_tag(self, tag: Tag): self.edit_modal = PanelModal( build_tag_panel, tag.name, # TODO - display name including subtags - "Edit Tag", + "Edit Tag", # TODO translate done_callback=self.driver.preview_panel.update_widgets, has_save=True, ) diff --git a/tagstudio/src/qt/widgets/video_player.py b/tagstudio/src/qt/widgets/video_player.py index 2b4434b1d..27d06b2b0 100644 --- a/tagstudio/src/qt/widgets/video_player.py +++ b/tagstudio/src/qt/widgets/video_player.py @@ -115,7 +115,7 @@ def __init__(self, driver: "QtDriver") -> None: self.setContextMenuPolicy(Qt.ContextMenuPolicy.ActionsContextMenu) self.opener = FileOpenerHelper(filepath=self.filepath) - autoplay_action = QAction("Autoplay", self) + autoplay_action = QAction("Autoplay", self) # TODO translate autoplay_action.setCheckable(True) self.addAction(autoplay_action) autoplay_action.setChecked( @@ -124,7 +124,7 @@ def __init__(self, driver: "QtDriver") -> None: autoplay_action.triggered.connect(lambda: self.toggle_autoplay()) self.autoplay = autoplay_action - open_file_action = QAction("Open file", self) + open_file_action = QAction("Open file", self) # TODO translate open_file_action.triggered.connect(self.opener.open_file) open_explorer_action = QAction(PlatformStrings.open_file_str, self) From 03b2e842fddf2ff9bd1ea02b242abf9310cb86b6 Mon Sep 17 00:00:00 2001 From: Jann Stute Date: Mon, 23 Dec 2024 19:21:50 +0100 Subject: [PATCH 08/26] feat: translate modals --- tagstudio/resources/translations/en.json | 63 ++++++++++++++----- tagstudio/src/qt/modals/add_field.py | 10 +-- tagstudio/src/qt/modals/build_tag.py | 25 +++++--- tagstudio/src/qt/modals/delete_unlinked.py | 33 ++++++---- tagstudio/src/qt/modals/drop_import.py | 40 +++++++----- tagstudio/src/qt/modals/file_extension.py | 18 ++++-- tagstudio/src/qt/modals/fix_dupes.py | 47 +++++++------- tagstudio/src/qt/modals/fix_unlinked.py | 34 +++++----- tagstudio/src/qt/modals/folders_to_tags.py | 12 ++-- tagstudio/src/qt/modals/merge_dupe_entries.py | 6 +- tagstudio/src/qt/modals/mirror_entities.py | 30 +++++---- tagstudio/src/qt/modals/relink_unlinked.py | 13 ++-- tagstudio/src/qt/modals/tag_database.py | 22 +++---- tagstudio/src/qt/modals/tag_search.py | 4 +- tagstudio/src/qt/translations.py | 4 +- tagstudio/src/qt/ts_qt.py | 2 +- 16 files changed, 216 insertions(+), 147 deletions(-) diff --git a/tagstudio/resources/translations/en.json b/tagstudio/resources/translations/en.json index 29fb89313..7d4378143 100644 --- a/tagstudio/resources/translations/en.json +++ b/tagstudio/resources/translations/en.json @@ -3,27 +3,31 @@ "app.pre_release": "Pre-Release", "app.title": "{base_title} - Library '{library_dir}'", "edit.tag_manager": "Manage Tags", - "entries.duplicate.merge.label": "Merging Duplicate Entries", + "entries.duplicate.merge.label": "Merging Duplicate Entries...", "entries.duplicate.merge": "Merge Duplicate Entries", "entries.duplicate.refresh": "Refresh Duplicate Entries", "entries.duplicates.description": "Duplicate entries are defined as multiple entries which point to the same file on disk. Merging these will combine the tags and metadata from all duplicates into a single consolidated entry. These are not to be confused with \"duplicate files\", which are duplicates of your files themselves outside of TagStudio.", - "entries.mirror.confirmation": "Are you sure you want to mirror the following %{len(self.lib.dupe_files)} Entries?", - "entries.mirror.label": "Mirroring 1/%{count} Entries...", + "entries.mirror.confirmation": "Are you sure you want to mirror the following {count} Entries?", + "entries.mirror.label": "Mirroring {idx}/{total} Entries...", "entries.mirror.title": "Mirroring Entries", - "entries.mirror": "Mirror", + "entries.mirror.window_title": "Mirror Entries", + "entries.mirror": "&Mirror", "entries.tags": "Tags", - "entries.unlinked.delete.confirm": "Are you sure you want to delete the following %{len(self.lib.missing_files)} entries?", - "entries.unlinked.delete.deleting_count": "Deleting %{x[0]+1}/{len(self.lib.missing_files)} Unlinked Entries", + "entries.unlinked.delete.confirm": "Are you sure you want to delete the following {count} entries?", + "entries.unlinked.delete.deleting_count": "Deleting {idx}/{count} Unlinked Entries", "entries.unlinked.delete.deleting": "Deleting Entries", "entries.unlinked.delete": "Delete Unlinked Entries", + "entries.unlinked.delete_alt": "De&lete Unlinked Entries", "entries.unlinked.description": "Each library entry is linked to a file in one of your directories. If a file linked to an entry is moved or deleted outside of TagStudio, it is then considered unlinked. Unlinked entries may be automatically relinked via searching your directories or deleted if desired.", - "entries.unlinked.refresh_all": "Refresh All", - "entries.unlinked.relink.attempting": "Attempting to Relink %{x[0]+1}/%{len(self.lib.missing_files)} Entries, %{self.fixed} Successfully Relinked", - "entries.unlinked.relink.manual": "Manual Relink", + "entries.unlinked.refresh_all": "&Refresh All", + "entries.unlinked.relink.attempting": "Attempting to Relink {idx}/{missing_count} Entries, {fixed_count} Successfully Relinked", + "entries.unlinked.relink.manual": "&Manual Relink", "entries.unlinked.relink.title": "Relinking Entries", "entries.unlinked.scanning": "Scanning Library for Unlinked Entries...", - "entries.unlinked.search_and_relink": "Search && Relink", + "entries.unlinked.search_and_relink": "&Search && Relink", "entries.unlinked.title": "Fix Unlinked Entries", + "entries.unlinked.missing_count.none": "Unlinked Entries: N/A", + "entries.unlinked.missing_count.some": "Unlinked Entries: {count}", "field.copy": "Copy Field", "field.edit": "Edit Field", "field.paste": "Paste Field", @@ -31,15 +35,16 @@ "file.date_created": "Date Created", "file.date_modified": "Date Modified", "file.dimensions": "Dimensions", + "file.duplicates.description": "TagStudio supports importing DupeGuru results to manage duplicate files.", "file.duplicates.dupeguru.advice": "After mirroring, you're free to use DupeGuru to delete the unwanted files. Afterwards, use TagStudio's \"Fix Unlinked Entries\" feature in the Tools menu in order to delete the unlinked Entries.", "file.duplicates.dupeguru.file_extension": "DupeGuru Files (*.dupeguru)", - "file.duplicates.dupeguru.load_file": "Load DupeGuru File", + "file.duplicates.dupeguru.load_file": "&Load DupeGuru File", "file.duplicates.dupeguru.no_file": "No DupeGuru File Selected", "file.duplicates.dupeguru.open_file": "Open DupeGuru Results File", "file.duplicates.fix": "Fix Duplicate Files", "file.duplicates.matches_uninitialized": "Duplicate File Matches: N/A", - "file.duplicates.matches": "Duplicate File Matches: %{count}", - "file.duplicates.mirror_entries": "Mirror Entries", + "file.duplicates.matches": "Duplicate File Matches: {count}", + "file.duplicates.mirror_entries": "&Mirror Entries", "file.duplicates.mirror.description": "Mirror the Entry data across each duplicate match set, combining all data while not removing or duplicating fields. This operation will not delete any files or data.", "file.duration": "Length", "file.not_found": "File Not Found", @@ -55,12 +60,23 @@ "folders_to_tags.title": "Create Tags From Folders", "generic.add": "Add", "generic.apply": "Apply", + "generic.apply_alt": "&Apply", "generic.cancel": "Cancel", + "generic.cancel_alt": "&Cancel", + "generic.close": "Close", "generic.copy": "Copy", "generic.cut": "Cut", "generic.delete": "Delete", + "generic.delete_alt": "&Delete", "generic.done": "Done", + "generic.done_alt": "&Done", "generic.edit": "Edit", + "generic.rename": "Rename", + "generic.rename_alt": "&Rename", + "generic.overwrite": "Overwrite", + "generic.overwrite_alt": "&Overwrite", + "generic.skip": "Skip", + "generic.skip_alt": "&Skip", "generic.navigation.back": "Back", "generic.navigation.next": "Next", "generic.paste": "Paste", @@ -76,10 +92,10 @@ "home.thumbnail_size.small": "Small Thumbnails", "home.thumbnail_size.mini": "Mini Thumbnails", "home.thumbnail_size": "Thumbnail Size", - "ignore_list.add_extension": "Add Extension", + "ignore_list.add_extension": "&Add Extension", "ignore_list.mode.exclude": "Exclude", "ignore_list.mode.include": "Include", - "ignore_list.mode.label": "List Mode", + "ignore_list.mode.label": "List Mode:", "ignore_list.title": "File Extensions", "library.field.add": "Add Field", "library.field.confirm_remove": "Are you sure you want to remove this \"%{self.lib.get_field_attr(field, \"name\")}\" field?", @@ -134,7 +150,10 @@ "status.results": "Results", "tag_manager.title": "Library Tags", "tag.add_to_search": "Add to Search", + "tag.edit": "Edit Tag", "tag.add": "Add Tag", + "tag.create": "Create Tag", + "tag.remove": "Remove Tag", "tag.aliases": "Aliases", "tag.color": "Color", "tag.name": "Name", @@ -144,13 +163,23 @@ "tag.parent_tags": "Parent Tags", "tag.search_for_tag": "Search for Tag", "tag.shorthand": "Shorthand", + "tag.remove_alias": "Remove selected alias", + "tag.tag_name_required": "Tag Name (Required)", "view.size.0": "Mini", "view.size.1": "Small", "view.size.2": "Medium", "view.size.3": "Large", "view.size.4": "Extra Large", - "window.button.close": "Close", "window.title.error": "Error", "window.title.open_create_library": "Open/Create Library", - "window.message.error_opening_library": "Error opening library." + "window.message.error_opening_library": "Error opening library.", + "tag_database.confirmation": "Are you sure you want to delete the tag \"{tag_name}\"?", + "drop_import.title": "Conflicting File(s)", + "drop_import.decription": "The following files have filenames already exist in the library", + "drop_import.progress.label.initial": "Importing New Files...", + "drop_import.progress.label.singular": "Importing New Files...\n1 File imported.{suffix}", + "drop_import.progress.label.plural": "Importing New Files...\n{count} Files Imported.{suffix}", + "drop_import.progress.window_title": "Import Files", + "drop_import.duplicates_choice.singular": "The following file has a filename that already exists in the library.", + "drop_import.duplicates_choice.plural": "The following {count} files have filenames that already exist in the library.", } \ No newline at end of file diff --git a/tagstudio/src/qt/modals/add_field.py b/tagstudio/src/qt/modals/add_field.py index 5e0a054dc..3e2a94b8b 100644 --- a/tagstudio/src/qt/modals/add_field.py +++ b/tagstudio/src/qt/modals/add_field.py @@ -15,6 +15,8 @@ ) from src.core.library import Library +from ..translations import Translations + class AddFieldModal(QWidget): done = Signal(list) @@ -26,7 +28,7 @@ def __init__(self, library: Library): super().__init__() self.is_connected = False self.lib = library - self.setWindowTitle("Add Field") # TODO translate + Translations.translate_with_setter(self.setWindowTitle, "library.field.add") self.setWindowModality(Qt.WindowModality.ApplicationModal) self.setMinimumSize(400, 300) self.root_layout = QVBoxLayout(self) @@ -40,7 +42,7 @@ def __init__(self, library: Library): # 'text-align:center;' "font-weight:bold;" "font-size:14px;" "padding-top: 6px" "" ) - self.title_widget.setText("Add Field") # TODO translate + Translations.translate_qobject(self.title_widget, "library.field.add") self.title_widget.setAlignment(Qt.AlignmentFlag.AlignCenter) self.list_widget = QListWidget() @@ -54,13 +56,13 @@ def __init__(self, library: Library): # self.cancel_button.setText('Cancel') self.cancel_button = QPushButton() - self.cancel_button.setText("Cancel") # TODO translate + Translations.translate_qobject(self.cancel_button, "generic.cancel") self.cancel_button.clicked.connect(self.hide) # self.cancel_button.clicked.connect(widget.reset) self.button_layout.addWidget(self.cancel_button) self.save_button = QPushButton() - self.save_button.setText("Add") # TODO translate + Translations.translate_qobject(self.save_button, "generic.add") # self.save_button.setAutoDefault(True) self.save_button.setDefault(True) self.save_button.clicked.connect(self.hide) diff --git a/tagstudio/src/qt/modals/build_tag.py b/tagstudio/src/qt/modals/build_tag.py index 8e77248db..bfdc136c9 100644 --- a/tagstudio/src/qt/modals/build_tag.py +++ b/tagstudio/src/qt/modals/build_tag.py @@ -29,6 +29,8 @@ from src.qt.widgets.panel import PanelModal, PanelWidget from src.qt.widgets.tag import TagAliasWidget, TagWidget +from ..translations import Translations + logger = structlog.get_logger(__name__) @@ -54,12 +56,14 @@ def __init__(self, library: Library, tag: Tag | None = None): self.name_layout.setSpacing(0) self.name_layout.setAlignment(Qt.AlignmentFlag.AlignLeft) self.name_title = QLabel() - self.name_title.setText("Name") + Translations.translate_qobject(self.name_title, "tag.name") self.name_layout.addWidget(self.name_title) self.name_field = QLineEdit() self.name_field.setFixedHeight(24) self.name_field.textChanged.connect(self.on_name_changed) - self.name_field.setPlaceholderText("Tag Name (Required)") # TODO translate + Translations.translate_with_setter( + self.name_field.setPlaceholderText, "tag.tag_name_required" + ) self.name_layout.addWidget(self.name_field) # Shorthand ------------------------------------------------------------ @@ -70,7 +74,7 @@ def __init__(self, library: Library, tag: Tag | None = None): self.shorthand_layout.setSpacing(0) self.shorthand_layout.setAlignment(Qt.AlignmentFlag.AlignLeft) self.shorthand_title = QLabel() - self.shorthand_title.setText("Shorthand") # TODO translate + Translations.translate_qobject(self.shorthand_title, "tag.shorthand") self.shorthand_layout.addWidget(self.shorthand_title) self.shorthand_field = QLineEdit() self.shorthand_layout.addWidget(self.shorthand_field) @@ -83,7 +87,7 @@ def __init__(self, library: Library, tag: Tag | None = None): self.aliases_layout.setSpacing(0) self.aliases_layout.setAlignment(Qt.AlignmentFlag.AlignLeft) self.aliases_title = QLabel() - self.aliases_title.setText("Aliases") # TODO translate + Translations.translate_qobject(self.aliases_title, "tag.aliases") self.aliases_layout.addWidget(self.aliases_title) self.aliases_flow_widget = QWidget() @@ -134,7 +138,7 @@ def __init__(self, library: Library, tag: Tag | None = None): self.subtags_layout.setAlignment(Qt.AlignmentFlag.AlignLeft) self.subtags_title = QLabel() - self.subtags_title.setText("Parent Tags") # TODO translate + Translations.translate_qobject(self.subtags_title, "tag.parent_tags") self.subtags_layout.addWidget(self.subtags_title) self.subtag_flow_widget = QWidget() @@ -180,7 +184,9 @@ def __init__(self, library: Library, tag: Tag | None = None): tsp = TagSearchPanel(self.lib, exclude_ids) tsp.tag_chosen.connect(lambda x: self.add_subtag_callback(x)) - self.add_tag_modal = PanelModal(tsp, "Add Parent Tags", "Add Parent Tags") # TODO translate + self.add_tag_modal = PanelModal(tsp) + Translations.translate_with_setter(self.add_tag_modal.setTitle, "tag.parent_tags.add") + Translations.translate_with_setter(self.add_tag_modal.setWindowTitle, "tag.parent_tags.add") self.subtags_add_button.clicked.connect(self.add_tag_modal.show) # self.subtags_layout.addWidget(self.subtags_add_button) @@ -196,7 +202,7 @@ def __init__(self, library: Library, tag: Tag | None = None): self.color_layout.setSpacing(0) self.color_layout.setAlignment(Qt.AlignmentFlag.AlignLeft) self.color_title = QLabel() - self.color_title.setText("Color") # TODO translate + Translations.translate_qobject(self.color_title, "tag.color") self.color_layout.addWidget(self.color_title) self.color_field = QComboBox() self.color_field.setEditable(False) @@ -218,7 +224,8 @@ def __init__(self, library: Library, tag: Tag | None = None): ) ) self.color_layout.addWidget(self.color_field) - remove_selected_alias_action = QAction("remove selected alias", self) # TODO translate + remove_selected_alias_action = QAction(self) + Translations.translate_qobject(remove_selected_alias_action, "tag.remove_alias") remove_selected_alias_action.triggered.connect(self.remove_selected_alias) remove_selected_alias_action.setShortcut( QtCore.QKeyCombination( @@ -243,7 +250,7 @@ def __init__(self, library: Library, tag: Tag | None = None): self.alias_names: set[str] = set() self.new_alias_names: dict = dict() - self.set_tag(tag or Tag(name="New Tag")) # TODO translate + self.set_tag(tag or Tag(name=Translations["tag.new"])) if tag is None: self.name_field.selectAll() diff --git a/tagstudio/src/qt/modals/delete_unlinked.py b/tagstudio/src/qt/modals/delete_unlinked.py index b1e1605ce..180c6e75b 100644 --- a/tagstudio/src/qt/modals/delete_unlinked.py +++ b/tagstudio/src/qt/modals/delete_unlinked.py @@ -17,6 +17,8 @@ from src.core.utils.missing_files import MissingRegistry from src.qt.widgets.progress import ProgressWidget +from ..translations import Translations + # Only import for type checking/autocompletion, will not be imported at runtime. if typing.TYPE_CHECKING: from src.qt.ts_qt import QtDriver @@ -29,7 +31,7 @@ def __init__(self, driver: "QtDriver", tracker: MissingRegistry): super().__init__() self.driver = driver self.tracker = tracker - self.setWindowTitle("Delete Unlinked Entries") # TODO translate + Translations.translate_with_setter(self.setWindowTitle, "entries.unlinked.delete") self.setWindowModality(Qt.WindowModality.ApplicationModal) self.setMinimumSize(500, 400) self.root_layout = QVBoxLayout(self) @@ -38,9 +40,11 @@ def __init__(self, driver: "QtDriver", tracker: MissingRegistry): self.desc_widget = QLabel() self.desc_widget.setObjectName("descriptionLabel") self.desc_widget.setWordWrap(True) - self.desc_widget.setText(f""" - Are you sure you want to delete the following {self.tracker.missing_files_count} entries? - """) # TODO translate + Translations.translate_qobject( + self.desc_widget, + "entries.unlinked.delete.confirm", + count=self.tracker.missing_files_count, + ) self.desc_widget.setAlignment(Qt.AlignmentFlag.AlignCenter) self.list_view = QListView() @@ -53,13 +57,13 @@ def __init__(self, driver: "QtDriver", tracker: MissingRegistry): self.button_layout.addStretch(1) self.cancel_button = QPushButton() - self.cancel_button.setText("&Cancel") # TODO translate + Translations.translate_qobject(self.cancel_button, "generic.cancel_alt") self.cancel_button.setDefault(True) self.cancel_button.clicked.connect(self.hide) self.button_layout.addWidget(self.cancel_button) self.delete_button = QPushButton() - self.delete_button.setText("&Delete") # TODO translate + Translations.translate_qobject(self.delete_button, "generic.delete_alt") self.delete_button.clicked.connect(self.hide) self.delete_button.clicked.connect(lambda: self.delete_entries()) self.button_layout.addWidget(self.delete_button) @@ -69,9 +73,11 @@ def __init__(self, driver: "QtDriver", tracker: MissingRegistry): self.root_layout.addWidget(self.button_container) def refresh_list(self): - self.desc_widget.setText(f""" - Are you sure you want to delete the following {self.tracker.missing_files_count} entries? - """) # TODO translate + self.desc_widget.setText( + Translations.translate_formatted( + "entries.unlinked.delete.confirm", count=self.tracker.missing_files_count + ) + ) self.model.clear() for i in self.tracker.missing_files: @@ -81,14 +87,17 @@ def refresh_list(self): def delete_entries(self): def displayed_text(x): - return f"Deleting {x}/{self.tracker.missing_files_count} Unlinked Entries" # TODO translate + return Translations.translate_formatted( + "entries.unlinked.delete.deleting_count", + idx=x, + count=self.tracker.missing_files_count, + ) pw = ProgressWidget( - window_title="Deleting Entries", # TODO translate - label_text="", cancel_button_text=None, minimum=0, maximum=self.tracker.missing_files_count, ) + Translations.translate_with_setter(pw.setWindowTitle, "entries.unlinked.delete.deleting") pw.from_iterable_function(self.tracker.execute_deletion, displayed_text, self.done.emit) diff --git a/tagstudio/src/qt/modals/drop_import.py b/tagstudio/src/qt/modals/drop_import.py index 7f4d77e85..7811a46de 100644 --- a/tagstudio/src/qt/modals/drop_import.py +++ b/tagstudio/src/qt/modals/drop_import.py @@ -19,6 +19,8 @@ ) from src.qt.widgets.progress import ProgressWidget +from ..translations import Translations + if TYPE_CHECKING: from src.qt.ts_qt import QtDriver @@ -41,7 +43,7 @@ def __init__(self, driver: "QtDriver"): self.driver: QtDriver = driver # Widget ====================== - self.setWindowTitle("Conflicting File(s)") # TODO translate + Translations.translate_with_setter(self.setWindowTitle, "drop_import.title") self.setWindowModality(Qt.WindowModality.ApplicationModal) self.setMinimumSize(500, 400) self.root_layout = QVBoxLayout(self) @@ -50,9 +52,7 @@ def __init__(self, driver: "QtDriver"): self.desc_widget = QLabel() self.desc_widget.setObjectName("descriptionLabel") self.desc_widget.setWordWrap(True) - self.desc_widget.setText( - "The following files have filenames already exist in the library" - ) # TODO translate + self.desc_widget.setText(Translations["drop_import.decription"]) self.desc_widget.setAlignment(Qt.AlignmentFlag.AlignCenter) # Duplicate File List ======== @@ -67,25 +67,25 @@ def __init__(self, driver: "QtDriver"): self.button_layout.addStretch(1) self.skip_button = QPushButton() - self.skip_button.setText("&Skip") # TODO translate + Translations.translate_qobject(self.skip_button, "generic.skip_alt") self.skip_button.setDefault(True) self.skip_button.clicked.connect(lambda: self.begin_transfer(DuplicateChoice.SKIP)) self.button_layout.addWidget(self.skip_button) self.overwrite_button = QPushButton() - self.overwrite_button.setText("&Overwrite") # TODO translate + Translations.translate_qobject(self.overwrite_button, "generic.overwrite_alt") self.overwrite_button.clicked.connect( lambda: self.begin_transfer(DuplicateChoice.OVERWRITE) ) self.button_layout.addWidget(self.overwrite_button) self.rename_button = QPushButton() - self.rename_button.setText("&Rename") # TODO translate + Translations.translate_qobject(self.rename_button, "generic.rename_alt") self.rename_button.clicked.connect(lambda: self.begin_transfer(DuplicateChoice.RENAME)) self.button_layout.addWidget(self.rename_button) self.cancel_button = QPushButton() - self.cancel_button.setText("&Cancel") # TODO translate + Translations.translate_qobject(self.cancel_button, "generic.cancel_alt") self.cancel_button.clicked.connect(lambda: self.begin_transfer(DuplicateChoice.CANCEL)) self.button_layout.addWidget(self.cancel_button) @@ -139,8 +139,12 @@ def collect_files_to_import(self, urls: list[QUrl]): def ask_duplicates_choice(self): """Display the message widgeth with a list of the duplicated files.""" self.desc_widget.setText( - f"The following {len(self.duplicate_files)} file(s) have filenames already exist in the library." # noqa: E501 - ) # TODO translate + Translations["drop_import.duplicates_choice.singular"] + if len(self.duplicate_files) == 1 + else Translations.translate_formatted( + "drop_import.duplicates_choice.plural", count=len(self.duplicate_files) + ) + ) self.model.clear() for dupe in self.duplicate_files: @@ -160,19 +164,21 @@ def begin_transfer(self, choice: DuplicateChoice | None = None): return def displayed_text(x): - text = f"Importing New Files...\n{x[0] + 1} File{'s' if x[0] + 1 != 1 else ''} Imported." # TODO translate - if self.choice: - text += f" {x[1]} {self.choice.value}" - - return text + return Translations.translate_formatted( + "drop_import.progress.label.singular" + if x[0] + 1 == 1 + else "drop_import.progress.label.plural", + count=x[0] + 1, + suffix=f" {x[1]} {self.choice.value}" if self.choice else "", + ) pw = ProgressWidget( - window_title="Import Files", # TODO translate - label_text="Importing New Files...", # TODO translate cancel_button_text=None, minimum=0, maximum=len(self.files), ) + Translations.translate_with_setter(pw.setWindowTitle, "drop_import.progress.window_title") + Translations.translate_with_setter(pw.update_label, "drop_import.progress.label.initial") pw.from_iterable_function( self.copy_files, diff --git a/tagstudio/src/qt/modals/file_extension.py b/tagstudio/src/qt/modals/file_extension.py index 19c6f06d1..45ea01990 100644 --- a/tagstudio/src/qt/modals/file_extension.py +++ b/tagstudio/src/qt/modals/file_extension.py @@ -20,6 +20,8 @@ from src.core.library import Library from src.qt.widgets.panel import PanelWidget +from ..translations import Translations + class FileExtensionItemDelegate(QStyledItemDelegate): def setModelData(self, editor, model, index): # noqa: N802 @@ -35,7 +37,7 @@ def __init__(self, library: "Library"): super().__init__() # Initialize Modal ===================================================== self.lib = library - self.setWindowTitle("File Extensions") # TODO translate + Translations.translate_with_setter(self.setWindowTitle, "ignore_list.title") self.setWindowModality(Qt.WindowModality.ApplicationModal) self.setMinimumSize(240, 400) self.root_layout = QVBoxLayout(self) @@ -50,7 +52,7 @@ def __init__(self, library: "Library"): # Create "Add Button" Widget ------------------------------------------- self.add_button = QPushButton() - self.add_button.setText("&Add Extension") # TODO translate + Translations.translate_qobject(self.add_button, "ignore_list.add_extension") self.add_button.clicked.connect(self.add_item) self.add_button.setDefault(True) self.add_button.setMinimumWidth(100) @@ -61,11 +63,17 @@ def __init__(self, library: "Library"): self.mode_layout.setContentsMargins(0, 0, 0, 0) self.mode_layout.setSpacing(12) self.mode_label = QLabel() - self.mode_label.setText("List Mode:") # TODO translate + Translations.translate_qobject(self.mode_label, "ignore_list.mode.label") self.mode_combobox = QComboBox() self.mode_combobox.setEditable(False) - self.mode_combobox.addItem("Include") # TODO translate - self.mode_combobox.addItem("Exclude") # TODO translate + self.mode_combobox.addItem("") + self.mode_combobox.addItem("") + Translations.translate_with_setter( + lambda text: self.mode_combobox.setItemText(0, text), "ignore_list.mode.include" + ) + Translations.translate_with_setter( + lambda text: self.mode_combobox.setItemText(0, text), "ignore_list.mode.exclude" + ) is_exclude_list = int(bool(self.lib.prefs(LibraryPrefs.IS_EXCLUDE_LIST))) diff --git a/tagstudio/src/qt/modals/fix_dupes.py b/tagstudio/src/qt/modals/fix_dupes.py index 0476e644e..12095d9fb 100644 --- a/tagstudio/src/qt/modals/fix_dupes.py +++ b/tagstudio/src/qt/modals/fix_dupes.py @@ -18,6 +18,8 @@ from src.core.utils.dupe_files import DupeRegistry from src.qt.modals.mirror_entities import MirrorEntriesModal +from ..translations import Translations + # Only import for type checking/autocompletion, will not be imported at runtime. if typing.TYPE_CHECKING: from src.qt.ts_qt import QtDriver @@ -30,7 +32,7 @@ def __init__(self, library: "Library", driver: "QtDriver"): self.driver = driver self.count = -1 self.filename = "" - self.setWindowTitle("Fix Duplicate Files") # TODO translate + Translations.translate_with_setter(self.setWindowTitle, "file.duplicates.fix") self.setWindowModality(Qt.WindowModality.ApplicationModal) self.setMinimumSize(400, 300) self.root_layout = QVBoxLayout(self) @@ -42,9 +44,7 @@ def __init__(self, library: "Library", driver: "QtDriver"): self.desc_widget.setObjectName("descriptionLabel") self.desc_widget.setWordWrap(True) self.desc_widget.setStyleSheet("text-align:left;") - self.desc_widget.setText( - "TagStudio supports importing DupeGuru results to manage duplicate files." - ) # TODO translate + Translations.translate_qobject(self.desc_widget, "file.duplicates.description") self.desc_widget.setAlignment(Qt.AlignmentFlag.AlignCenter) self.dupe_count = QLabel() @@ -54,34 +54,25 @@ def __init__(self, library: "Library", driver: "QtDriver"): self.file_label = QLabel() self.file_label.setObjectName("fileLabel") - self.file_label.setText("No DupeGuru File Selected") # TODO translate + Translations.translate_qobject(self.file_label, "file.duplicates.dupeguru.no_file") self.open_button = QPushButton() - self.open_button.setText("&Load DupeGuru File") # TODO translate + Translations.translate_qobject(self.open_button, "file.duplicates.dupeguru.load_file") self.open_button.clicked.connect(self.select_file) self.mirror_modal = MirrorEntriesModal(self.driver, self.tracker) self.mirror_modal.done.connect(self.refresh_dupes) self.mirror_button = QPushButton() - self.mirror_button.setText("&Mirror Entries") # TODO translate + Translations.translate_qobject(self.mirror_button, "file.duplicates.mirror_entries") self.mirror_button.clicked.connect(self.mirror_modal.show) self.mirror_desc = QLabel() self.mirror_desc.setWordWrap(True) - self.mirror_desc.setText( - "Mirror the Entry data across each duplicate match set, combining all data while not " - "removing or duplicating fields. This operation will not delete any files or data." - ) # TODO translate + Translations.translate_qobject(self.mirror_desc, "file.duplicates.mirror.description") self.advice_label = QLabel() self.advice_label.setWordWrap(True) - # fmt: off - self.advice_label.setText( - "After mirroring, you're free to use DupeGuru to delete the unwanted files. " - "Afterwards, use TagStudio's \"Fix Unlinked Entries\" feature in the " - "Tools menu in order to delete the unlinked Entries." - )# TODO translate - # fmt: on + Translations.translate_qobject(self.advice_label, "file.duplicates.dupeguru.advice") self.button_container = QWidget() self.button_layout = QHBoxLayout(self.button_container) @@ -89,7 +80,7 @@ def __init__(self, library: "Library", driver: "QtDriver"): self.button_layout.addStretch(1) self.done_button = QPushButton() - self.done_button.setText("&Done") # TODO translate + Translations.translate_qobject(self.done_button, "generic.done_alt") self.done_button.setDefault(True) self.done_button.clicked.connect(self.hide) self.button_layout.addWidget(self.done_button) @@ -109,10 +100,10 @@ def __init__(self, library: "Library", driver: "QtDriver"): def select_file(self): qfd = QFileDialog( - self, "Open DupeGuru Results File", str(self.lib.library_dir) - ) # TODO translate + self, Translations["file.duplicates.dupeguru.open_file"], str(self.lib.library_dir) + ) qfd.setFileMode(QFileDialog.FileMode.ExistingFile) - qfd.setNameFilter("DupeGuru Files (*.dupeguru)") # TODO translate + qfd.setNameFilter(Translations["file.duplicates.dupeguru.file_extension"]) if qfd.exec_(): filename = qfd.selectedFiles() if filename: @@ -122,7 +113,7 @@ def set_filename(self, filename: str): if filename: self.file_label.setText(filename) else: - self.file_label.setText("No DupeGuru File Selected") # TODO translate + self.file_label.setText(Translations["file.duplicates.dupeguru.no_file"]) self.filename = filename self.refresh_dupes() self.mirror_modal.refresh_list() @@ -134,10 +125,14 @@ def refresh_dupes(self): def set_dupe_count(self, count: int): if count < 0: self.mirror_button.setDisabled(True) - self.dupe_count.setText("Duplicate File Matches: N/A") # TODO translate + self.dupe_count.setText(Translations["file.duplicates.matches_uninitialized"]) elif count == 0: self.mirror_button.setDisabled(True) - self.dupe_count.setText(f"Duplicate File Matches: {count}") # TODO translate + self.dupe_count.setText( + Translations.translate_formatted("file.duplicates.matches", count=count) + ) else: self.mirror_button.setDisabled(False) - self.dupe_count.setText(f"Duplicate File Matches: {count}") # TODO translate + self.dupe_count.setText( + Translations.translate_formatted("file.duplicates.matches", count=count) + ) diff --git a/tagstudio/src/qt/modals/fix_unlinked.py b/tagstudio/src/qt/modals/fix_unlinked.py index 2afae7fb6..3226566f1 100644 --- a/tagstudio/src/qt/modals/fix_unlinked.py +++ b/tagstudio/src/qt/modals/fix_unlinked.py @@ -14,6 +14,8 @@ from src.qt.modals.relink_unlinked import RelinkUnlinkedEntries from src.qt.widgets.progress import ProgressWidget +from ..translations import Translations + # Only import for type checking/autocompletion, will not be imported at runtime. if typing.TYPE_CHECKING: from src.qt.ts_qt import QtDriver @@ -29,7 +31,7 @@ def __init__(self, library: "Library", driver: "QtDriver"): self.missing_count = -1 self.dupe_count = -1 - self.setWindowTitle("Fix Unlinked Entries") # TODO translate + Translations.translate_with_setter(self.setWindowTitle, "entries.unlinked.title") self.setWindowModality(Qt.WindowModality.ApplicationModal) self.setMinimumSize(400, 300) self.root_layout = QVBoxLayout(self) @@ -39,13 +41,7 @@ def __init__(self, library: "Library", driver: "QtDriver"): self.unlinked_desc_widget.setObjectName("unlinkedDescriptionLabel") self.unlinked_desc_widget.setWordWrap(True) self.unlinked_desc_widget.setStyleSheet("text-align:left;") - self.unlinked_desc_widget.setText( - "Each library entry is linked to a file in one of your directories. " - "If a file linked to an entry is moved or deleted outside of TagStudio, " - "it is then considered unlinked.\n\n" - "Unlinked entries may be automatically relinked via searching your directories, " - "manually relinked by the user, or deleted if desired." - ) # TODO translate + Translations.translate_qobject(self.unlinked_desc_widget, "entries.unlinked.description") self.missing_count_label = QLabel() self.missing_count_label.setObjectName("missingCountLabel") @@ -58,14 +54,14 @@ def __init__(self, library: "Library", driver: "QtDriver"): self.dupe_count_label.setAlignment(Qt.AlignmentFlag.AlignCenter) self.refresh_unlinked_button = QPushButton() - self.refresh_unlinked_button.setText("&Refresh All") # TODO translate + Translations.translate_qobject(self.refresh_unlinked_button, "entries.unlinked.refresh_all") self.refresh_unlinked_button.clicked.connect(self.refresh_missing_files) self.merge_class = MergeDuplicateEntries(self.lib, self.driver) self.relink_class = RelinkUnlinkedEntries(self.tracker) self.search_button = QPushButton() - self.search_button.setText("&Search && Relink") # TODO translate + Translations.translate_qobject(self.search_button, "entries.unlinked.search_and_relink") self.relink_class.done.connect( # refresh the grid lambda: ( @@ -76,7 +72,7 @@ def __init__(self, library: "Library", driver: "QtDriver"): self.search_button.clicked.connect(self.relink_class.repair_entries) self.manual_button = QPushButton() - self.manual_button.setText("&Manual Relink") # TODO translate + Translations.translate_qobject(self.manual_button, "entries.unlinked.relink.manual") self.manual_button.setHidden(True) self.delete_button = QPushButton() @@ -88,7 +84,7 @@ def __init__(self, library: "Library", driver: "QtDriver"): self.driver.filter_items(), ) ) - self.delete_button.setText("De&lete Unlinked Entries") # TODO translate + Translations.translate_qobject(self.delete_button, "entries.unlinked.delete_alt") self.delete_button.clicked.connect(self.delete_modal.show) self.button_container = QWidget() @@ -97,7 +93,7 @@ def __init__(self, library: "Library", driver: "QtDriver"): self.button_layout.addStretch(1) self.done_button = QPushButton() - self.done_button.setText("&Done") # TODO translate + Translations.translate_qobject(self.done_button, "generic.done_alt") self.done_button.setDefault(True) self.done_button.clicked.connect(self.hide) self.button_layout.addWidget(self.done_button) @@ -116,12 +112,12 @@ def __init__(self, library: "Library", driver: "QtDriver"): def refresh_missing_files(self): pw = ProgressWidget( - window_title="Scanning Library", # TODO translate - label_text="Scanning Library for Unlinked Entries...", # TODO translate cancel_button_text=None, minimum=0, maximum=self.lib.entries_count, ) + Translations.translate_with_setter(pw.setWindowTitle, "library.scan_library.title") + Translations.translate_with_setter(pw.update_label, "entries.unlinked.scanning") pw.from_iterable_function( self.tracker.refresh_missing_files, @@ -139,11 +135,13 @@ def set_missing_count(self, count: int | None = None): if self.missing_count < 0: self.search_button.setDisabled(True) self.delete_button.setDisabled(True) - self.missing_count_label.setText("Unlinked Entries: N/A") # TODO translate + self.missing_count_label.setText(Translations["entries.unlinked.missing_count.none"]) else: # disable buttons if there are no files to fix self.search_button.setDisabled(self.missing_count == 0) self.delete_button.setDisabled(self.missing_count == 0) self.missing_count_label.setText( - f"Unlinked Entries: {self.missing_count}" - ) # TODO translate + Translations.translate_formatted( + "entries.unlinked.missing_count.some", count=self.missing_count + ) + ) diff --git a/tagstudio/src/qt/modals/folders_to_tags.py b/tagstudio/src/qt/modals/folders_to_tags.py index ca2d48ccb..60fcd3187 100644 --- a/tagstudio/src/qt/modals/folders_to_tags.py +++ b/tagstudio/src/qt/modals/folders_to_tags.py @@ -24,6 +24,8 @@ from src.core.palette import ColorType, get_tag_color from src.qt.flowlayout import FlowLayout +from ..translations import Translations + if typing.TYPE_CHECKING: from src.qt.ts_qt import QtDriver @@ -164,7 +166,7 @@ def __init__(self, library: "Library", driver: "QtDriver"): self.count = -1 self.filename = "" - self.setWindowTitle("Create Tags From Folders") # TODO translate + Translations.translate_with_setter(self.setWindowTitle, "folders_to_tags.title") self.setWindowModality(Qt.WindowModality.ApplicationModal) self.setMinimumSize(640, 640) self.root_layout = QVBoxLayout(self) @@ -174,7 +176,7 @@ def __init__(self, library: "Library", driver: "QtDriver"): self.title_widget.setObjectName("title") self.title_widget.setWordWrap(True) self.title_widget.setStyleSheet("font-weight:bold;" "font-size:14px;" "padding-top: 6px") - self.title_widget.setText("Create Tags From Folders") # TODO translate + Translations.translate_qobject(self.title_widget, "folders_to_tags.title") self.title_widget.setAlignment(Qt.AlignmentFlag.AlignCenter) self.desc_widget = QLabel() @@ -190,10 +192,10 @@ def __init__(self, library: "Library", driver: "QtDriver"): self.open_close_button_layout = QHBoxLayout(self.open_close_button_w) self.open_all_button = QPushButton() - self.open_all_button.setText("Open All") # TODO translate + Translations.translate_qobject(self.open_all_button, "folders_to_tags.open_all") self.open_all_button.clicked.connect(lambda: self.set_all_branches(False)) self.close_all_button = QPushButton() - self.close_all_button.setText("Close All") # TODO translate + Translations.translate_qobject(self.close_all_button, "folders_to_tags.close_all") self.close_all_button.clicked.connect(lambda: self.set_all_branches(True)) self.open_close_button_layout.addWidget(self.open_all_button) @@ -212,7 +214,7 @@ def __init__(self, library: "Library", driver: "QtDriver"): self.scroll_area.setWidget(self.scroll_contents) self.apply_button = QPushButton() - self.apply_button.setText("&Apply") # TODO translate + Translations.translate_qobject(self.apply_button, "generic.apply_alt") self.apply_button.setMinimumWidth(100) self.apply_button.clicked.connect(self.on_apply) diff --git a/tagstudio/src/qt/modals/merge_dupe_entries.py b/tagstudio/src/qt/modals/merge_dupe_entries.py index 14891d47a..5d2d49af9 100644 --- a/tagstudio/src/qt/modals/merge_dupe_entries.py +++ b/tagstudio/src/qt/modals/merge_dupe_entries.py @@ -9,6 +9,8 @@ from src.core.utils.dupe_files import DupeRegistry from src.qt.widgets.progress import ProgressWidget +from ..translations import Translations + # Only import for type checking/autocompletion, will not be imported at runtime. if typing.TYPE_CHECKING: from src.qt.ts_qt import QtDriver @@ -25,11 +27,11 @@ def __init__(self, library: "Library", driver: "QtDriver"): def merge_entries(self): pw = ProgressWidget( - window_title="Merging Duplicate Entries", # TODO translate - label_text="Merging Duplicate Entries...", # TODO translate cancel_button_text=None, minimum=0, maximum=self.tracker.groups_count, ) + Translations.translate_with_setter(pw.setWindowTitle, "entries.duplicate.merge.label") + Translations.translate_with_setter(pw.update_label, "entries.duplicate.merge.label") pw.from_iterable_function(self.tracker.merge_dupe_entries, None, self.done.emit) diff --git a/tagstudio/src/qt/modals/mirror_entities.py b/tagstudio/src/qt/modals/mirror_entities.py index 4c0cd4aa4..c872fa58e 100644 --- a/tagstudio/src/qt/modals/mirror_entities.py +++ b/tagstudio/src/qt/modals/mirror_entities.py @@ -19,6 +19,8 @@ from src.core.utils.dupe_files import DupeRegistry from src.qt.widgets.progress import ProgressWidget +from ..translations import Translations + # Only import for type checking/autocompletion, will not be imported at runtime. if typing.TYPE_CHECKING: from src.qt.ts_qt import QtDriver @@ -30,7 +32,7 @@ class MirrorEntriesModal(QWidget): def __init__(self, driver: "QtDriver", tracker: DupeRegistry): super().__init__() self.driver = driver - self.setWindowTitle("Mirror Entries") # TODO translate + Translations.translate_with_setter(self.setWindowTitle, "entries.mirror.window_title") self.setWindowModality(Qt.WindowModality.ApplicationModal) self.setMinimumSize(500, 400) self.root_layout = QVBoxLayout(self) @@ -40,10 +42,9 @@ def __init__(self, driver: "QtDriver", tracker: DupeRegistry): self.desc_widget = QLabel() self.desc_widget.setObjectName("descriptionLabel") self.desc_widget.setWordWrap(True) - - self.desc_widget.setText(f""" - Are you sure you want to mirror the following {self.tracker.groups_count} Entries? - """) # TODO translate + Translations.translate_qobject( + self.desc_widget, "entries.mirror.confirmation", count=self.tracker.groups_count + ) self.desc_widget.setAlignment(Qt.AlignmentFlag.AlignCenter) self.list_view = QListView() @@ -56,13 +57,13 @@ def __init__(self, driver: "QtDriver", tracker: DupeRegistry): self.button_layout.addStretch(1) self.cancel_button = QPushButton() - self.cancel_button.setText("&Cancel") # TODO translate + Translations.translate_qobject(self.cancel_button, "generic.cancel_alt") self.cancel_button.setDefault(True) self.cancel_button.clicked.connect(self.hide) self.button_layout.addWidget(self.cancel_button) self.mirror_button = QPushButton() - self.mirror_button.setText("&Mirror") # TODO translate + Translations.translate_qobject(self.mirror_button, "entries.mirror") self.mirror_button.clicked.connect(self.hide) self.mirror_button.clicked.connect(self.mirror_entries) self.button_layout.addWidget(self.mirror_button) @@ -72,9 +73,11 @@ def __init__(self, driver: "QtDriver", tracker: DupeRegistry): self.root_layout.addWidget(self.button_container) def refresh_list(self): - self.desc_widget.setText(f""" - Are you sure you want to mirror the following {self.tracker.groups_count} Entries? - """) + self.desc_widget.setText( + Translations.translate_formatted( + "entries.mirror.confirmation", count=self.tracker.groups_count + ) + ) self.model.clear() for i in self.tracker.groups: @@ -82,15 +85,16 @@ def refresh_list(self): def mirror_entries(self): def displayed_text(x): - return f"Mirroring {x + 1}/{self.tracker.groups_count} Entries..." # TODO translate + return Translations.translate_formatted( + "entries.mirror.label", idx=x + 1, count=self.tracker.groups_count + ) pw = ProgressWidget( - window_title="Mirroring Entries", # TODO translate - label_text="", cancel_button_text=None, minimum=0, maximum=self.tracker.groups_count, ) + Translations.translate_with_setter(pw.setWindowTitle, "entries.mirror.title") pw.from_iterable_function( self.mirror_entries_runnable, diff --git a/tagstudio/src/qt/modals/relink_unlinked.py b/tagstudio/src/qt/modals/relink_unlinked.py index 87d67a6c0..366930a75 100644 --- a/tagstudio/src/qt/modals/relink_unlinked.py +++ b/tagstudio/src/qt/modals/relink_unlinked.py @@ -7,6 +7,8 @@ from src.core.utils.missing_files import MissingRegistry from src.qt.widgets.progress import ProgressWidget +from ..translations import Translations + class RelinkUnlinkedEntries(QObject): done = Signal() @@ -17,16 +19,19 @@ def __init__(self, tracker: MissingRegistry): def repair_entries(self): def displayed_text(x): - text = f"Attempting to Relink {x}/{self.tracker.missing_files_count} Entries. \n" # TODO translate - text += f"{self.tracker.files_fixed_count} Successfully Relinked." # TODO translate - return text + return Translations.translate_formatted( + "entries.unlinked.relink.attempting", + idx=x, + missing_count=self.tracker.missing_files_count, + fixed_count=self.tracker.files_fixed_count, + ) pw = ProgressWidget( - window_title="Relinking Entries", # TODO translate label_text="", cancel_button_text=None, minimum=0, maximum=self.tracker.missing_files_count, ) + Translations.translate_with_setter(pw.setWindowTitle, "entries.unlinked.relink.title") pw.from_iterable_function(self.tracker.fix_missing_files, displayed_text, self.done.emit) diff --git a/tagstudio/src/qt/modals/tag_database.py b/tagstudio/src/qt/modals/tag_database.py index 15f6289e5..8a308cd8a 100644 --- a/tagstudio/src/qt/modals/tag_database.py +++ b/tagstudio/src/qt/modals/tag_database.py @@ -21,6 +21,8 @@ from src.qt.widgets.panel import PanelModal, PanelWidget from src.qt.widgets.tag import TagWidget +from ..translations import Translations + logger = structlog.get_logger(__name__) # TODO: This class shares the majority of its code with tag_search.py. @@ -44,7 +46,7 @@ def __init__(self, library: Library): self.search_field = QLineEdit() self.search_field.setObjectName("searchField") self.search_field.setMinimumSize(QSize(0, 32)) - self.search_field.setPlaceholderText("Search Tags") # TODO translate + Translations.translate_with_setter(self.search_field.setPlaceholderText, "home.search_tags") self.search_field.textEdited.connect(lambda: self.update_tags(self.search_field.text())) self.search_field.returnPressed.connect( lambda checked=False: self.on_return(self.search_field.text()) @@ -63,7 +65,7 @@ def __init__(self, library: Library): self.scroll_area.setWidget(self.scroll_contents) self.create_tag_button = QPushButton() - self.create_tag_button.setText("Create Tag") # TODO translate + Translations.translate_qobject(self.create_tag_button, "tag.create") self.create_tag_button.clicked.connect(self.build_tag) self.root_layout.addWidget(self.search_field) @@ -72,14 +74,14 @@ def __init__(self, library: Library): self.update_tags() def build_tag(self): + panel = BuildTagPanel(self.lib) self.modal = PanelModal( - BuildTagPanel(self.lib), - "New Tag", # TODO translate - "Add Tag", # TODO translate + panel, has_save=True, ) + Translations.translate_with_setter(self.modal.setTitle, "tag.new") + Translations.translate_with_setter(self.modal.setWindowTitle, "tag.add") - panel: BuildTagPanel = self.modal.widget self.modal.saved.connect( lambda: ( self.lib.add_tag( @@ -134,10 +136,8 @@ def remove_tag(self, tag: Tag): return message_box = QMessageBox() - message_box.setWindowTitle("Remove Tag") # TODO translate - message_box.setText( - f'Are you sure you want to delete the tag "{tag.name}"?' - ) # TODO translate + Translations.translate_with_setter(message_box.setWindowTitle, "tag.remove") + Translations.translate_qobject(message_box, "tag_database.confirmation", tag_name=tag.name) message_box.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel) # type: ignore message_box.setIcon(QMessageBox.Question) # type: ignore @@ -158,10 +158,10 @@ def edit_tag(self, tag: Tag): self.edit_modal = PanelModal( build_tag_panel, tag.name, - "Edit Tag", # TODO translate done_callback=(self.update_tags(self.search_field.text())), has_save=True, ) + Translations.translate_with_setter(self.edit_modal.setWindowTitle, "tag.edit") # TODO Check Warning: Expected type 'BuildTagPanel', got 'PanelWidget' instead self.edit_modal.saved.connect(lambda: self.edit_tag_callback(build_tag_panel)) self.edit_modal.show() diff --git a/tagstudio/src/qt/modals/tag_search.py b/tagstudio/src/qt/modals/tag_search.py index 2189b3164..63cac736e 100644 --- a/tagstudio/src/qt/modals/tag_search.py +++ b/tagstudio/src/qt/modals/tag_search.py @@ -22,6 +22,8 @@ from src.qt.widgets.panel import PanelWidget from src.qt.widgets.tag import TagWidget +from ..translations import Translations + logger = structlog.get_logger(__name__) @@ -41,7 +43,7 @@ def __init__(self, library: Library, exclude: list[int] | None = None): self.search_field = QLineEdit() self.search_field.setObjectName("searchField") self.search_field.setMinimumSize(QSize(0, 32)) - self.search_field.setPlaceholderText("Search Tags") # TODO translate + Translations.translate_with_setter(self.search_field.setPlaceholderText, "home.search_tags") self.search_field.textEdited.connect(lambda: self.update_tags(self.search_field.text())) self.search_field.returnPressed.connect( lambda checked=False: self.on_return(self.search_field.text()) diff --git a/tagstudio/src/qt/translations.py b/tagstudio/src/qt/translations.py index 057c5da79..882c37128 100644 --- a/tagstudio/src/qt/translations.py +++ b/tagstudio/src/qt/translations.py @@ -4,7 +4,7 @@ import ujson from PySide6.QtCore import QObject, Signal from PySide6.QtGui import QAction -from PySide6.QtWidgets import QLabel, QMenu, QPushButton +from PySide6.QtWidgets import QLabel, QMenu, QMessageBox, QPushButton DEFAULT_TRANSLATION = "de" @@ -49,7 +49,7 @@ def change_language(self, lang: str): def translate_qobject(self, widget: QObject, key: str, **kwargs): """Translates the text of the QObject using :func:`translate_with_setter`.""" - if isinstance(widget, (QLabel, QAction, QPushButton)): + if isinstance(widget, (QLabel, QAction, QPushButton, QMessageBox)): self.translate_with_setter(widget.setText, key, **kwargs) elif isinstance(widget, (QMenu)): self.translate_with_setter(widget.setTitle, key, **kwargs) diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index a6c205c93..62fa51fce 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -522,7 +522,7 @@ def show_error_message(self, message: str): msg_box.setIcon(QMessageBox.Icon.Critical) msg_box.setText(message) msg_box.setWindowTitle(Translations["window.title.error"]) - msg_box.addButton(Translations["window.button.close"], QMessageBox.ButtonRole.AcceptRole) + msg_box.addButton(Translations["generic.close"], QMessageBox.ButtonRole.AcceptRole) # Show the message box msg_box.exec() From e2c1440fb97f3e3acbc1d338003d012168084623 Mon Sep 17 00:00:00 2001 From: Jann Stute Date: Mon, 23 Dec 2024 20:28:14 +0100 Subject: [PATCH 09/26] feat: translate more stuff --- tagstudio/resources/translations/en.json | 7 ++++- tagstudio/src/qt/main_window.py | 34 ++++++------------------ tagstudio/src/qt/platform_strings.py | 8 +++--- tagstudio/src/qt/ui/home_ui.py | 8 +++--- tagstudio/src/qt/widgets/item_thumb.py | 8 ++++-- tagstudio/src/qt/widgets/landing.py | 6 ++++- 6 files changed, 34 insertions(+), 37 deletions(-) diff --git a/tagstudio/resources/translations/en.json b/tagstudio/resources/translations/en.json index 024d1cd84..95a1d3a3e 100644 --- a/tagstudio/resources/translations/en.json +++ b/tagstudio/resources/translations/en.json @@ -50,6 +50,9 @@ "file.not_found": "File Not Found", "file.open_file_with": "Open file with", "file.open_file": "Open file", + "file.open_file.windows": "Open file in Explorer", + "file.open_file.macos": "Open file in Finder", + "file.open_file.other": "Open file in file explorer", "file.open_location.generic": "Show file in explorer", "file.open_location.mac": "Reveal in Finder", "file.open_location.windows": "Show in File Explorer", @@ -81,6 +84,7 @@ "generic.navigation.next": "Next", "generic.paste": "Paste", "generic.recent_libraries": "Recent Libraries", + "generic.filename": "Filename", "help.visit_github": "Visit GitHub Repository", "home.search_entries": "Search Entries", "home.search_library": "Search Library", @@ -180,5 +184,6 @@ "drop_import.progress.label.plural": "Importing New Files...\n{count} Files Imported.{suffix}", "drop_import.progress.window_title": "Import Files", "drop_import.duplicates_choice.singular": "The following file has a filename that already exists in the library.", - "drop_import.duplicates_choice.plural": "The following {count} files have filenames that already exist in the library." + "drop_import.duplicates_choice.plural": "The following {count} files have filenames that already exist in the library.", + "landing.open_create_library": "Open/Create Library {shortcut}" } \ No newline at end of file diff --git a/tagstudio/src/qt/main_window.py b/tagstudio/src/qt/main_window.py index 4d755f051..77bf338ab 100644 --- a/tagstudio/src/qt/main_window.py +++ b/tagstudio/src/qt/main_window.py @@ -25,6 +25,8 @@ from src.qt.pagination import Pagination from src.qt.widgets.landing import LandingWidget +from .translations import Translations + # Only import for type checking/autocompletion, will not be imported at runtime. if typing.TYPE_CHECKING: from src.qt.ts_qt import QtDriver @@ -77,6 +79,8 @@ def setupUi(self, MainWindow): # Thumbnail Size placeholder self.thumb_size_combobox = QComboBox(self.centralwidget) self.thumb_size_combobox.setObjectName(u"thumbSizeComboBox") + Translations.translate_with_setter(self.thumb_size_combobox.setPlaceholderText, "home.thumbnail_size") + self.thumb_size_combobox.setCurrentText("") sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -128,7 +132,7 @@ def setupUi(self, MainWindow): self.horizontalLayout_2 = QHBoxLayout() self.horizontalLayout_2.setObjectName(u"horizontalLayout_2") self.horizontalLayout_2.setSizeConstraint(QLayout.SetMinimumSize) - self.backButton = QPushButton(self.centralwidget) + self.backButton = QPushButton("<", self.centralwidget) self.backButton.setObjectName(u"backButton") self.backButton.setMinimumSize(QSize(0, 32)) self.backButton.setMaximumSize(QSize(32, 16777215)) @@ -139,7 +143,7 @@ def setupUi(self, MainWindow): self.horizontalLayout_2.addWidget(self.backButton) - self.forwardButton = QPushButton(self.centralwidget) + self.forwardButton = QPushButton(">", self.centralwidget) self.forwardButton.setObjectName(u"forwardButton") self.forwardButton.setMinimumSize(QSize(0, 32)) self.forwardButton.setMaximumSize(QSize(32, 16777215)) @@ -152,6 +156,7 @@ def setupUi(self, MainWindow): self.horizontalLayout_2.addWidget(self.forwardButton) self.searchField = QLineEdit(self.centralwidget) + Translations.translate_with_setter(self.searchField.setPlaceholderText, "home.search_entries") self.searchField.setObjectName(u"searchField") self.searchField.setMinimumSize(QSize(0, 32)) font2 = QFont() @@ -167,6 +172,7 @@ def setupUi(self, MainWindow): self.horizontalLayout_2.addWidget(self.searchField) self.searchButton = QPushButton(self.centralwidget) + Translations.translate_qobject(self.searchButton, "home.search") self.searchButton.setObjectName(u"searchButton") self.searchButton.setMinimumSize(QSize(0, 32)) self.searchButton.setFont(font2) @@ -186,33 +192,9 @@ def setupUi(self, MainWindow): self.statusbar.setSizePolicy(sizePolicy1) MainWindow.setStatusBar(self.statusbar) - self.retranslateUi(MainWindow) - QMetaObject.connectSlotsByName(MainWindow) # setupUi - def retranslateUi(self, MainWindow): - MainWindow.setWindowTitle(QCoreApplication.translate( - "MainWindow", u"MainWindow", None)) - # Navigation buttons - self.backButton.setText( - QCoreApplication.translate("MainWindow", u"<", None)) #TODO translate - self.forwardButton.setText( - QCoreApplication.translate("MainWindow", u">", None)) #TODO translate - - # Search field - self.searchField.setPlaceholderText( - QCoreApplication.translate("MainWindow", u"Search Entries", None)) #TODO translate - self.searchButton.setText( - QCoreApplication.translate("MainWindow", u"Search", None)) #TODO translate - - self.thumb_size_combobox.setCurrentText("") - - # Thumbnail size selector - self.thumb_size_combobox.setPlaceholderText( - QCoreApplication.translate("MainWindow", u"Thumbnail Size", None)) #TODO translate - # retranslateUi - def moveEvent(self, event) -> None: # time.sleep(0.02) # sleep for 20ms pass diff --git a/tagstudio/src/qt/platform_strings.py b/tagstudio/src/qt/platform_strings.py index 7131da897..ae2339ddb 100644 --- a/tagstudio/src/qt/platform_strings.py +++ b/tagstudio/src/qt/platform_strings.py @@ -6,11 +6,13 @@ import platform +from .translations import Translations + class PlatformStrings: - open_file_str: str = "Open in file explorer" + open_file_str: str = Translations["file.open_file.other"] if platform.system() == "Windows": - open_file_str = "Open in Explorer" # TODO translate + open_file_str = Translations["file.open_file.windows"] elif platform.system() == "Darwin": - open_file_str = "Reveal in Finder" # TODO translate + open_file_str = Translations["file.open_file.macos"] diff --git a/tagstudio/src/qt/ui/home_ui.py b/tagstudio/src/qt/ui/home_ui.py index 3474f9573..0d986f6ad 100644 --- a/tagstudio/src/qt/ui/home_ui.py +++ b/tagstudio/src/qt/ui/home_ui.py @@ -147,14 +147,14 @@ def setupUi(self, MainWindow): def retranslateUi(self, MainWindow): MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"MainWindow", None)) - self.comboBox_2.setItemText(0, QCoreApplication.translate("MainWindow", u"And (includes all tags)", None)) # TODO remove? - self.comboBox_2.setItemText(1, QCoreApplication.translate("MainWindow", u"Or (includes any tag)", None)) # TODO remove? + self.comboBox_2.setItemText(0, QCoreApplication.translate("MainWindow", u"And (includes all tags)", None)) + self.comboBox_2.setItemText(1, QCoreApplication.translate("MainWindow", u"Or (includes any tag)", None)) self.comboBox.setCurrentText("") - self.comboBox.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Thumbnail Size", None)) # TODO translate? + self.comboBox.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Thumbnail Size", None)) self.backButton.setText(QCoreApplication.translate("MainWindow", u"<", None)) self.forwardButton.setText(QCoreApplication.translate("MainWindow", u">", None)) - self.searchField.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Search Entries", None)) # TODO translate? + self.searchField.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Search Entries", None)) self.searchButton.setText(QCoreApplication.translate("MainWindow", u"Search", None)) # retranslateUi diff --git a/tagstudio/src/qt/widgets/item_thumb.py b/tagstudio/src/qt/widgets/item_thumb.py index 48ea0dbd7..78a644b93 100644 --- a/tagstudio/src/qt/widgets/item_thumb.py +++ b/tagstudio/src/qt/widgets/item_thumb.py @@ -33,6 +33,8 @@ from src.qt.widgets.thumb_button import ThumbButton from src.qt.widgets.thumb_renderer import ThumbRenderer +from ..translations import Translations + if TYPE_CHECKING: from src.qt.ts_qt import QtDriver @@ -217,7 +219,8 @@ def __init__( self.thumb_button.setContextMenuPolicy(Qt.ContextMenuPolicy.ActionsContextMenu) self.opener = FileOpenerHelper("") - open_file_action = QAction("Open file", self) # TODO translate + open_file_action = QAction(self) + Translations.translate_qobject(open_file_action, "file.open_file") open_file_action.triggered.connect(self.opener.open_file) open_explorer_action = QAction(PlatformStrings.open_file_str, self) open_explorer_action.triggered.connect(self.opener.open_explorer) @@ -306,7 +309,8 @@ def __init__( self.cb_layout.addWidget(badge) # Filename Label ======================================================= - self.file_label = QLabel(text="Filename") # TODO translate + self.file_label = QLabel() + Translations.translate_qobject(self.file_label, "generic.filename") self.file_label.setStyleSheet(ItemThumb.filename_style) self.file_label.setMaximumHeight(self.label_height) if not show_filename_label: diff --git a/tagstudio/src/qt/widgets/landing.py b/tagstudio/src/qt/widgets/landing.py index 935268ef1..6f3b6e433 100644 --- a/tagstudio/src/qt/widgets/landing.py +++ b/tagstudio/src/qt/widgets/landing.py @@ -15,6 +15,8 @@ from src.qt.helpers.color_overlay import gradient_overlay, theme_fg_overlay from src.qt.widgets.clickable_label import ClickableLabel +from ..translations import Translations + # Only import for type checking/autocompletion, will not be imported at runtime. if typing.TYPE_CHECKING: from src.qt.ts_qt import QtDriver @@ -62,7 +64,9 @@ def __init__(self, driver: "QtDriver", pixel_ratio: float): open_shortcut_text = "(Ctrl+O)" self.open_button: QPushButton = QPushButton() self.open_button.setMinimumWidth(200) - self.open_button.setText(f"Open/Create Library {open_shortcut_text}") # TODO translate + Translations.translate_qobject( + self.open_button, "landing.open_create_library", shortcut=open_shortcut_text + ) self.open_button.clicked.connect(self.driver.open_library_from_dialog) # Create status label -------------------------------------------------- From 2f79458750d05c8067758107448a8be6ab0acedb Mon Sep 17 00:00:00 2001 From: Jann Stute Date: Mon, 23 Dec 2024 20:28:32 +0100 Subject: [PATCH 10/26] fix: UI test wasn't comparing to translated strings --- tagstudio/tests/qt/test_build_tag_panel.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tagstudio/tests/qt/test_build_tag_panel.py b/tagstudio/tests/qt/test_build_tag_panel.py index 87971bcc8..d0cdea5b0 100644 --- a/tagstudio/tests/qt/test_build_tag_panel.py +++ b/tagstudio/tests/qt/test_build_tag_panel.py @@ -1,5 +1,6 @@ from src.core.library.alchemy.models import Tag from src.qt.modals.build_tag import BuildTagPanel +from src.qt.translations import Translations def test_build_tag_panel_add_sub_tag_callback(library, generate_tag): @@ -155,4 +156,4 @@ def test_build_tag_panel_build_tag(library): tag: Tag = panel.build_tag() assert tag - assert tag.name == "New Tag" + assert tag.name == Translations["tag.new"] From 05ee989e22999c5e2e8ff5c71d8f020a7babe855 Mon Sep 17 00:00:00 2001 From: Jann Stute Date: Mon, 23 Dec 2024 21:33:09 +0100 Subject: [PATCH 11/26] feat: translations for most of the remaining stuff --- tagstudio/resources/translations/en.json | 43 +++++- tagstudio/src/qt/translations.py | 4 +- tagstudio/src/qt/ts_qt.py | 2 +- tagstudio/src/qt/widgets/migration_modal.py | 158 +++++++++++--------- tagstudio/src/qt/widgets/panel.py | 8 +- tagstudio/src/qt/widgets/preview_panel.py | 18 ++- tagstudio/src/qt/widgets/tag.py | 11 +- tagstudio/src/qt/widgets/tag_box.py | 9 +- tagstudio/src/qt/widgets/video_player.py | 8 +- 9 files changed, 167 insertions(+), 94 deletions(-) diff --git a/tagstudio/resources/translations/en.json b/tagstudio/resources/translations/en.json index 95a1d3a3e..71fdfb6af 100644 --- a/tagstudio/resources/translations/en.json +++ b/tagstudio/resources/translations/en.json @@ -69,11 +69,14 @@ "generic.close": "Close", "generic.copy": "Copy", "generic.cut": "Cut", + "generic.save": "Save", "generic.delete": "Delete", "generic.delete_alt": "&Delete", + "generic.continue": "Continue", "generic.done": "Done", "generic.done_alt": "&Done", "generic.edit": "Edit", + "generic.edit_alt": "&Edit", "generic.rename": "Rename", "generic.rename_alt": "&Rename", "generic.overwrite": "Overwrite", @@ -102,7 +105,7 @@ "ignore_list.mode.label": "List Mode:", "ignore_list.title": "File Extensions", "library.field.add": "Add Field", - "library.field.confirm_remove": "Are you sure you want to remove this \"%{self.lib.get_field_attr(field, \"name\")}\" field?", + "library.field.confirm_remove": "Are you sure you want to remove this \"{name}\" field?", "library.field.mixed_data": "Mixed Data", "library.field.remove": "Remove Field", "library.missing": "Library Location is Missing", @@ -118,7 +121,7 @@ "menu.edit.manage_file_extensions": "Manage File Extensions", "menu.edit.manage_tags": "Manage Tags", "menu.edit.new_tag": "New &Tag", - "menu.edit": "&Edit", + "menu.edit": "Edit", "menu.file.close_library": "&Close Library", "menu.file.new_library": "New Library", "menu.file.open_create_library": "&Open/Create Library", @@ -156,6 +159,7 @@ "tag.add_to_search": "Add to Search", "tag.edit": "Edit Tag", "tag.add": "Add Tag", + "tag.add.plural": "Add Tags", "tag.create": "Create Tag", "tag.remove": "Remove Tag", "tag.aliases": "Aliases", @@ -185,5 +189,38 @@ "drop_import.progress.window_title": "Import Files", "drop_import.duplicates_choice.singular": "The following file has a filename that already exists in the library.", "drop_import.duplicates_choice.plural": "The following {count} files have filenames that already exist in the library.", - "landing.open_create_library": "Open/Create Library {shortcut}" + "landing.open_create_library": "Open/Create Library {shortcut}", + "video_player.autoplay": "Autoplay", + "json_migration.title": "Save Format Migration: \"{path}\"", + "json_migration.info.description": "Library save files created with TagStudio versions 9.4 and below will need to be migrated to the new v9.5+ format.

What you need to know:

  • Your existing library save file will NOT be deleted
  • Your personal files will NOT be deleted, moved, or modified
  • The new v9.5+ save format can not be opened in earlier versions of TagStudio
", + "json_migration.title.old_lib": "

v9.4 Library

", + "json_migration.title.new_lib": "

v9.5+ Library

", + "json_migration.start_and_preview": "Start and Preview", + "json_migration.finish_migration": "Finish Migration", + "json_migration.checking_for_parity": "Checking for Parity...", + "json_migration.creating_database_tables": "Creating SQL Database Tables...", + "json_migration.migrating_files_entries": "Migrating {entries:,d} File Entries...", + "json_migration.migration_complete": "Migration Complete!", + "json_migration.migration_complete_with_discrepancies": "Migration Complete, Discrepancies Found", + "json_migration.discrepancies_found": "Library Discrepancies Found", + "json_migration.discrepancies_found.description": "Discrepancies were found between the original and converted library formats. Please review and choose to whether continue with the migration or to cancel.", + "json_migration.discrepancies.sql_entry_id_missing": "[Field Comparison]:\nNEW (SQL): SQL Entry ID not found: {id}", + "json_migration.discrepancies.fields": "[Field Comparison]:\nOLD (JSON):{json_fields}\nNEW (SQL):{sql_fields}", + "json_migration.discrepancies.subtag": "[Subtag Parity]:\nOLD (JSON):{json_subtags}\nNEW (SQL):{sql_subtags}", + "json_migration.discrepancies.alias": "[Alias Parity]:\nOLD (JSON):{json_aliases}\nNEW (SQL):{sql_aliases}", + "json_migration.discrepancies.shorthand": "[Shorthand Parity]:\nOLD (JSON):{json_shorthand}\nNEW (SQL):{sql_shorthand}", + "json_migration.discrepancies.color": "[Color Parity]:\nOLD (JSON):{json_color}\nNEW (SQL):{sql_color}", + "json_migration.heading.match": "Matched", + "json_migration.heading.differ": "Discrepancy", + "json_migration.heading.entires": "Entries:", + "json_migration.heading.tags": "Tags:", + "json_migration.heading.shorthands": "Shorthands:", + "json_migration.heading.parent_tags": "Parent Tags:", + "json_migration.heading.aliases": "Aliases:", + "json_migration.heading.colors": "Colors:", + "json_migration.heading.file_extension_list": "File Extension List:", + "json_migration.heading.extension_list_type": "Extension List Type:", + "json_migration.heading.paths": "Paths:", + "json_migration.heading.fields": "Fields:", + "json_migration.description": "
Start and preview the results of the library migration process. The converted library will not be used unless you click \"Finish Migration\".

Library data should either have matching values or a feature a \"Matched\" label. Values that do not match will be displayed in red and feature a \"(!)\" symbol next to them.
This process may take up to several minutes for larger libraries.
" } \ No newline at end of file diff --git a/tagstudio/src/qt/translations.py b/tagstudio/src/qt/translations.py index 882c37128..553cf5627 100644 --- a/tagstudio/src/qt/translations.py +++ b/tagstudio/src/qt/translations.py @@ -6,6 +6,8 @@ from PySide6.QtGui import QAction from PySide6.QtWidgets import QLabel, QMenu, QMessageBox, QPushButton +from .helpers.qbutton_wrapper import QPushButtonWrapper + DEFAULT_TRANSLATION = "de" @@ -49,7 +51,7 @@ def change_language(self, lang: str): def translate_qobject(self, widget: QObject, key: str, **kwargs): """Translates the text of the QObject using :func:`translate_with_setter`.""" - if isinstance(widget, (QLabel, QAction, QPushButton, QMessageBox)): + if isinstance(widget, (QLabel, QAction, QPushButton, QMessageBox, QPushButtonWrapper)): self.translate_with_setter(widget.setText, key, **kwargs) elif isinstance(widget, (QMenu)): self.translate_with_setter(widget.setTitle, key, **kwargs) diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index 948608d2f..530eda3f1 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -259,7 +259,7 @@ def start(self) -> None: file_menu = QMenu(menu_bar) Translations.translate_qobject(file_menu, "menu.file") edit_menu = QMenu(menu_bar) - Translations.translate_qobject(edit_menu, "menu.edit") + Translations.translate_qobject(edit_menu, "generic.edit_alt") view_menu = QMenu(menu_bar) Translations.translate_qobject(view_menu, "menu.view") tools_menu = QMenu(menu_bar) diff --git a/tagstudio/src/qt/widgets/migration_modal.py b/tagstudio/src/qt/widgets/migration_modal.py index 2bf13a95a..eeb5d4d26 100644 --- a/tagstudio/src/qt/widgets/migration_modal.py +++ b/tagstudio/src/qt/widgets/migration_modal.py @@ -34,6 +34,8 @@ from src.qt.widgets.paged_panel.paged_panel import PagedPanel from src.qt.widgets.paged_panel.paged_panel_state import PagedPanelState +from ..translations import Translations + logger = structlog.get_logger(__name__) @@ -54,7 +56,7 @@ def __init__(self, path: Path): self.is_migration_initialized: bool = False self.discrepancies: list[str] = [] - self.title: str = f'Save Format Migration: "{self.path}"' # TODO translate + self.title: str = Translations.translate_formatted("json_migration.title", path=self.path) self.warning: str = "(!)" self.old_entry_count: int = 0 @@ -77,24 +79,17 @@ def __init__(self, path: Path): def init_page_info(self) -> None: """Initialize the migration info page.""" body_wrapper: PagedBodyWrapper = PagedBodyWrapper() - body_label: QLabel = QLabel( # TODO translate - "Library save files created with TagStudio versions 9.4 and below will " - "need to be migrated to the new v9.5+ format." - "
" - "

What you need to know:

" - "
    " - "
  • Your existing library save file will NOT be deleted
  • " - "
  • Your personal files will NOT be deleted, moved, or modified
  • " - "
  • The new v9.5+ save format can not be opened in earlier versions of TagStudio
  • " - "
" - ) + body_label = QLabel() + Translations.translate_qobject(body_label, "json_migration.info.description") body_label.setWordWrap(True) body_label.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) body_wrapper.layout().addWidget(body_label) body_wrapper.layout().setContentsMargins(0, 36, 0, 0) - cancel_button: QPushButtonWrapper = QPushButtonWrapper("Cancel") # TODO translate - next_button: QPushButtonWrapper = QPushButtonWrapper("Continue") # TODO translate + cancel_button = QPushButtonWrapper() + Translations.translate_qobject(cancel_button, "generic.cancel") + next_button = QPushButtonWrapper() + Translations.translate_qobject(next_button, "generic.continue") cancel_button.clicked.connect(self.migration_cancelled.emit) self.stack.append( @@ -115,30 +110,20 @@ def init_page_convert(self) -> None: body_container_layout.setContentsMargins(0, 0, 0, 0) tab: str = " " - self.match_text: str = "Matched" # TODO translate - self.differ_text: str = "Discrepancy" # TODO translate - - entries_text: str = "Entries:" # TODO translate - tags_text: str = "Tags:" # TODO translate - shorthand_text: str = tab + "Shorthands:" # TODO translate - subtags_text: str = tab + "Parent Tags:" # TODO translate - aliases_text: str = tab + "Aliases:" # TODO translate - colors_text: str = tab + "Colors:" # TODO translate - ext_text: str = "File Extension List:" # TODO translate - ext_type_text: str = "Extension List Type:" # TODO translate - desc_text: str = ( # TODO translate - "
Start and preview the results of the library migration process. " - 'The converted library will not be used unless you click "Finish Migration". ' - "

" - 'Library data should either have matching values or a feature a "Matched" label. ' - 'Values that do not match will be displayed in red and feature a "(!)" ' - "symbol next to them." - "
" - "This process may take up to several minutes for larger libraries." - "
" - ) - path_parity_text: str = tab + "Paths:" # TODO translate - field_parity_text: str = tab + "Fields:" # TODO translate + self.match_text: str = Translations["json_migration.heading.match"] + self.differ_text: str = Translations["json_migration.heading.differ"] + + entries_text: str = Translations["json_migration.heading.entires"] + tags_text: str = Translations["json_migration.heading.tags"] + shorthand_text: str = tab + Translations["json_migration.heading.shorthands"] + subtags_text: str = tab + Translations["json_migration.heading.parent_tags"] + aliases_text: str = tab + Translations["json_migration.heading.aliases"] + colors_text: str = tab + Translations["json_migration.heading.colors"] + ext_text: str = Translations["json_migration.heading.file_extension_list"] + ext_type_text: str = Translations["json_migration.heading.extension_list_type"] + desc_text: str = Translations["json_migration.description"] + path_parity_text: str = tab + Translations["json_migration.heading.paths"] + field_parity_text: str = tab + Translations["json_migration.heading.fields"] self.entries_row: int = 0 self.path_row: int = 1 @@ -153,7 +138,8 @@ def init_page_convert(self) -> None: old_lib_container: QWidget = QWidget() old_lib_layout: QVBoxLayout = QVBoxLayout(old_lib_container) - old_lib_title: QLabel = QLabel("

v9.4 Library

") # TODO translate + old_lib_title = QLabel() + Translations.translate_qobject(old_lib_title, "json_migration.title.old_lib") old_lib_title.setAlignment(Qt.AlignmentFlag.AlignCenter) old_lib_layout.addWidget(old_lib_title) @@ -215,7 +201,8 @@ def init_page_convert(self) -> None: new_lib_container: QWidget = QWidget() new_lib_layout: QVBoxLayout = QVBoxLayout(new_lib_container) - new_lib_title: QLabel = QLabel("

v9.5+ Library

") # TODO translate + new_lib_title = QLabel() + Translations.translate_qobject(new_lib_title, "json_migration.title.new_lib") new_lib_title.setAlignment(Qt.AlignmentFlag.AlignCenter) new_lib_layout.addWidget(new_lib_title) @@ -291,13 +278,16 @@ def init_page_convert(self) -> None: self.body_wrapper_01.layout().addWidget(desc_label) self.body_wrapper_01.layout().setSpacing(12) - back_button: QPushButtonWrapper = QPushButtonWrapper("Back") # TODO translate - start_button: QPushButtonWrapper = QPushButtonWrapper("Start and Preview") # TODO translate + back_button = QPushButtonWrapper() + Translations.translate_qobject(back_button, "generic.navigation.back") + start_button = QPushButtonWrapper() + Translations.translate_qobject(start_button, "json_migration.start_and_preview") start_button.setMinimumWidth(120) start_button.clicked.connect(self.migrate) start_button.clicked.connect(lambda: finish_button.setDisabled(False)) start_button.clicked.connect(lambda: start_button.setDisabled(True)) - finish_button: QPushButtonWrapper = QPushButtonWrapper("Finish Migration") # TODO translate + finish_button: QPushButtonWrapper = QPushButtonWrapper() + Translations.translate_qobject(finish_button, "json_migration.finish_migration") finish_button.setMinimumWidth(120) finish_button.setDisabled(True) finish_button.clicked.connect(self.finish_migration) @@ -348,9 +338,11 @@ def migration_progress(self, skip_ui: bool = False): lambda x: ( pb.setLabelText(f"

{x}

"), self.update_sql_value_ui(show_msg_box=False) - if x == "Checking for Parity..." # TODO translate + if x == Translations["json_migration.checking_for_parity"] + else (), + self.update_parity_ui() + if x == Translations["json_migration.checking_for_parity"] else (), - self.update_parity_ui() if x == "Checking for Parity..." else (), # TODO translate ) ) r = CustomRunnable(iterator.run) @@ -367,23 +359,23 @@ def migration_iterator(self): """Iterate over the library migration process.""" try: # Convert JSON Library to SQLite - yield "Creating SQL Database Tables..." # TODO translate + yield Translations["json_migration.creating_database_tables"] self.sql_lib = SqliteLibrary() self.temp_path: Path = ( self.json_lib.library_dir / TS_FOLDER_NAME / "migration_ts_library.sqlite" ) self.sql_lib.storage_path = self.temp_path if self.temp_path.exists(): - logger.info( - 'Temporary migration file "temp_path" already exists. Removing...' - ) # TODO translate + logger.info('Temporary migration file "temp_path" already exists. Removing...') self.temp_path.unlink() self.sql_lib.open_sqlite_library( self.json_lib.library_dir, is_new=True, add_default_data=False ) - yield f"Migrating {len(self.json_lib.entries):,d} File Entries..." # TODO translate + yield Translations.translate_formatted( + "json_migration.migrating_files_entries", entries=len(self.json_lib.entries) + ) self.sql_lib.migrate_json_to_sqlite(self.json_lib) - yield "Checking for Parity..." # TODO translate + yield Translations["json_migration.checking_for_parity"] check_set = set() check_set.add(self.check_field_parity()) check_set.add(self.check_path_parity()) @@ -393,9 +385,9 @@ def migration_iterator(self): check_set.add(self.check_color_parity()) self.update_parity_ui() if False not in check_set: - yield "Migration Complete!" # TODO translate + yield Translations["json_migration.migration_complete"] else: - yield "Migration Complete, Discrepancies Found" # TODO translate + yield Translations["json_migration.migration_complete_with_discrepancies"] self.done = True except Exception as e: @@ -434,18 +426,19 @@ def update_sql_value_ui(self, show_msg_box: bool = True): self.sql_lib.prefs(LibraryPrefs.IS_EXCLUDE_LIST), self.old_ext_type, ) - logger.info("Parity check complete!") # TODO translate + logger.info("Parity check complete!") if self.discrepancies: - logger.warning("Discrepancies found:") # TODO translate + logger.warning("Discrepancies found:") logger.warning("\n".join(self.discrepancies)) QApplication.beep() if not show_msg_box: return msg_box = QMessageBox() - msg_box.setWindowTitle("Library Discrepancies Found") # TODO translate - msg_box.setText( # TODO translate - "Discrepancies were found between the original and converted library formats. " - "Please review and choose to whether continue with the migration or to cancel." + Translations.translate_with_setter( + msg_box.setWindowTitle, "json_migration.discrepancies_found" + ) + Translations.translate_qobject( + msg_box, "json_migration.discrepancies_found.description" ) msg_box.setDetailedText("\n".join(self.discrepancies)) msg_box.setIcon(QMessageBox.Icon.Warning) @@ -548,8 +541,11 @@ def sanitize_json_field(value): "[Field Comparison]", message=f"NEW (SQL): SQL Entry ID mismatch: {json_entry.id+1}", ) - self.discrepancies.append( # TODO translate - f"[Field Comparison]:\nNEW (SQL): SQL Entry ID not found: {json_entry.id+1}" + self.discrepancies.append( + Translations.translate_formatted( + "json_migration.discrepancies.sql_entry_id_missing", + id=json_entry.id + 1, + ) ) self.field_parity = False return self.field_parity @@ -626,8 +622,12 @@ def sanitize_json_field(value): and sql_fields is not None and (json_fields == sql_fields) ): - self.discrepancies.append( # TODO translate - f"[Field Comparison]:\nOLD (JSON):{json_fields}\nNEW (SQL):{sql_fields}" + self.discrepancies.append( + Translations.translate_formatted( + "json_migration.discrepancies.fields", + json_fields=json_fields, + sql_fields=sql_fields, + ) ) self.field_parity = False return self.field_parity @@ -678,8 +678,12 @@ def check_subtag_parity(self) -> bool: and json_subtags is not None and (sql_subtags == json_subtags) ): - self.discrepancies.append( # TODO translate - f"[Subtag Parity]:\nOLD (JSON):{json_subtags}\nNEW (SQL):{sql_subtags}" + self.discrepancies.append( + Translations.translate_formatted( + "json_migration.discrepancies.subtag", + json_subtags=json_subtags, + sql_subtags=sql_subtags, + ) ) self.subtag_parity = False return self.subtag_parity @@ -714,8 +718,12 @@ def check_alias_parity(self) -> bool: and json_aliases is not None and (sql_aliases == json_aliases) ): - self.discrepancies.append( # TODO translate - f"[Alias Parity]:\nOLD (JSON):{json_aliases}\nNEW (SQL):{sql_aliases}" + self.discrepancies.append( + Translations.translate_formatted( + "json_migration.discrepancies.alias", + json_aliases=json_aliases, + sql_aliases=sql_aliases, + ) ) self.alias_parity = False return self.alias_parity @@ -745,8 +753,12 @@ def check_shorthand_parity(self) -> bool: and json_shorthand is not None and (sql_shorthand == json_shorthand) ): - self.discrepancies.append( # TODO translate - f"[Shorthand Parity]:\nOLD (JSON):{json_shorthand}\nNEW (SQL):{sql_shorthand}" + self.discrepancies.append( + Translations.translate_formatted( + "json_migration.discrepancies.shorthand", + json_shorthand=json_shorthand, + sql_shorthand=sql_shorthand, + ) ) self.shorthand_parity = False return self.shorthand_parity @@ -776,8 +788,12 @@ def check_color_parity(self) -> bool: ) if not (sql_color is not None and json_color is not None and (sql_color == json_color)): - self.discrepancies.append( # TODO translate - f"[Color Parity]:\nOLD (JSON):{json_color}\nNEW (SQL):{sql_color}" + self.discrepancies.append( + Translations.translate_formatted( + "json_migration.discrepancies.color", + json_color=json_color, + sql_color=sql_color, + ) ) self.color_parity = False return self.color_parity diff --git a/tagstudio/src/qt/widgets/panel.py b/tagstudio/src/qt/widgets/panel.py index 59e2234c8..8f11a5be7 100755 --- a/tagstudio/src/qt/widgets/panel.py +++ b/tagstudio/src/qt/widgets/panel.py @@ -7,6 +7,8 @@ from PySide6.QtCore import Qt, Signal from PySide6.QtWidgets import QHBoxLayout, QLabel, QPushButton, QVBoxLayout, QWidget +from ..translations import Translations + class PanelModal(QWidget): saved = Signal() @@ -49,7 +51,7 @@ def __init__( if not (save_callback or has_save): self.done_button = QPushButton() - self.done_button.setText("Done") # TODO translate + Translations.translate_qobject(self.done_button, "generic.done") self.done_button.setAutoDefault(True) self.done_button.clicked.connect(self.hide) if done_callback: @@ -59,7 +61,7 @@ def __init__( if save_callback or has_save: self.cancel_button = QPushButton() - self.cancel_button.setText("Cancel") # TODO translate + Translations.translate_qobject(self.cancel_button, "generic.cancel") self.cancel_button.clicked.connect(self.hide) self.cancel_button.clicked.connect(widget.reset) # self.cancel_button.clicked.connect(cancel_callback) @@ -67,7 +69,7 @@ def __init__( self.button_layout.addWidget(self.cancel_button) self.save_button = QPushButton() - self.save_button.setText("Save") # TODO translate + Translations.translate_qobject(self.save_button, "generic.save") self.save_button.setAutoDefault(True) self.save_button.clicked.connect(self.hide) self.save_button.clicked.connect(self.saved.emit) diff --git a/tagstudio/src/qt/widgets/preview_panel.py b/tagstudio/src/qt/widgets/preview_panel.py index 50ed08424..051ed28e5 100644 --- a/tagstudio/src/qt/widgets/preview_panel.py +++ b/tagstudio/src/qt/widgets/preview_panel.py @@ -62,6 +62,8 @@ from src.qt.widgets.thumb_renderer import ThumbRenderer from src.qt.widgets.video_player import VideoPlayer +from ..translations import Translations + if typing.TYPE_CHECKING: from src.qt.ts_qt import QtDriver @@ -119,7 +121,8 @@ def __init__(self, library: Library, driver: "QtDriver"): ) date_style = "font-size:12px;" - self.open_file_action = QAction("Open file", self) # TODO translate + self.open_file_action = QAction(self) + Translations.translate_qobject(self.open_file_action, "file.open_file") self.open_explorer_action = QAction(PlatformStrings.open_file_str, self) self.preview_img = QPushButtonWrapper() @@ -279,7 +282,7 @@ def __init__(self, library: Library, driver: "QtDriver"): self.add_field_button.setCursor(Qt.CursorShape.PointingHandCursor) self.add_field_button.setMinimumSize(96, 28) self.add_field_button.setMaximumSize(96, 28) - self.add_field_button.setText("Add Field") # TODO translate + Translations.translate_qobject(self.add_field_button, "library.field.add") self.afb_layout.addWidget(self.add_field_button) self.add_field_modal = AddFieldModal(self.lib) self.place_add_field_button() @@ -303,7 +306,7 @@ def update_selected_entry(self, driver: "QtDriver"): self.driver.frame_content[grid_idx] = result def remove_field_prompt(self, name: str) -> str: - return f'Are you sure you want to remove field "{name}"?' # TODO translate + return Translations.translate_formatted("library.field.confirm_remove", name=name) def fill_libs_widget(self, layout: QVBoxLayout): settings = self.driver.settings @@ -341,7 +344,8 @@ def clear_layout(layout_item: QVBoxLayout): # remove any potential previous items clear_layout(layout) - label = QLabel("Recent Libraries") # TODO translate + label = QLabel() + Translations.translate_qobject(label, "generic.recent_libraries") label.setAlignment(Qt.AlignmentFlag.AlignCenter) row_layout = QHBoxLayout() @@ -379,7 +383,7 @@ def set_button_style( lib = Path(full_val) if not lib.exists() or not (lib / TS_FOLDER_NAME).exists(): button.setDisabled(True) - button.setToolTip("Location is missing") # TODO translate + Translations.translate_with_setter(button.setToolTip, "library.missing") def open_library_button_clicked(path): return lambda: self.driver.open_library(Path(path)) @@ -1096,8 +1100,8 @@ def remove_message_box(self, prompt: str, callback: Callable) -> None: remove_mb.setWindowTitle("Remove Field") # TODO translate remove_mb.setIcon(QMessageBox.Icon.Warning) cancel_button = remove_mb.addButton( - "&Cancel", QMessageBox.ButtonRole.DestructiveRole - ) # TODO translate + Translations["generic.cancel_alt"], QMessageBox.ButtonRole.DestructiveRole + ) remove_mb.addButton("&Remove", QMessageBox.ButtonRole.RejectRole) # TODO translate # remove_mb.setStandardButtons(QMessageBox.StandardButton.Cancel) remove_mb.setDefaultButton(cancel_button) diff --git a/tagstudio/src/qt/widgets/tag.py b/tagstudio/src/qt/widgets/tag.py index 21bb4f6d7..7cd7f697b 100644 --- a/tagstudio/src/qt/widgets/tag.py +++ b/tagstudio/src/qt/widgets/tag.py @@ -21,6 +21,8 @@ from src.core.library.alchemy.enums import TagColor from src.core.palette import ColorType, get_tag_color +from ..translations import Translations + class TagAliasWidget(QWidget): on_remove = Signal() @@ -126,17 +128,20 @@ def __init__( self.bg_button.setFlat(True) self.bg_button.setText(tag.name) if has_edit: - edit_action = QAction("Edit", self) # TODO translate + edit_action = QAction(self) + Translations.translate_qobject(edit_action, "generic.edit") edit_action.triggered.connect(on_edit_callback) edit_action.triggered.connect(self.on_edit.emit) self.bg_button.addAction(edit_action) # if on_click_callback: self.bg_button.setContextMenuPolicy(Qt.ContextMenuPolicy.ActionsContextMenu) - search_for_tag_action = QAction("Search for Tag", self) # TODO translate + search_for_tag_action = QAction(self) + Translations.translate_qobject(search_for_tag_action, "tag.search_for_tag") search_for_tag_action.triggered.connect(self.on_click.emit) self.bg_button.addAction(search_for_tag_action) - add_to_search_action = QAction("Add to Search", self) # TODO translate + add_to_search_action = QAction(self) + Translations.translate_qobject(add_to_search_action, "tag.add_to_search") self.bg_button.addAction(add_to_search_action) self.inner_layout = QHBoxLayout() diff --git a/tagstudio/src/qt/widgets/tag_box.py b/tagstudio/src/qt/widgets/tag_box.py index 5a59b25a4..26f666a6e 100755 --- a/tagstudio/src/qt/widgets/tag_box.py +++ b/tagstudio/src/qt/widgets/tag_box.py @@ -20,6 +20,8 @@ from src.qt.widgets.panel import PanelModal from src.qt.widgets.tag import TagWidget +from ..translations import Translations + if typing.TYPE_CHECKING: from src.qt.ts_qt import QtDriver @@ -75,7 +77,8 @@ def __init__( ) tsp = TagSearchPanel(self.driver.lib) tsp.tag_chosen.connect(lambda x: self.add_tag_callback(x)) - self.add_modal = PanelModal(tsp, title, "Add Tags") # TODO translate + self.add_modal = PanelModal(tsp, title) + Translations.translate_with_setter(self.add_modal.setWindowTitle, "tag.add.plural") self.add_button.clicked.connect( lambda: ( tsp.update_tags(), @@ -130,11 +133,11 @@ def edit_tag(self, tag: Tag): self.edit_modal = PanelModal( build_tag_panel, - tag.name, # TODO - display name including subtags - "Edit Tag", # TODO translate + title=tag.name, # TODO - display name including subtags done_callback=self.driver.preview_panel.update_widgets, has_save=True, ) + Translations.translate_with_setter(self.edit_modal.setWindowTitle, "tag.edit") # TODO - this was update_tag() self.edit_modal.saved.connect( lambda: self.driver.lib.update_tag( diff --git a/tagstudio/src/qt/widgets/video_player.py b/tagstudio/src/qt/widgets/video_player.py index 27d06b2b0..187140e97 100644 --- a/tagstudio/src/qt/widgets/video_player.py +++ b/tagstudio/src/qt/widgets/video_player.py @@ -32,6 +32,8 @@ from src.qt.helpers.file_opener import FileOpenerHelper from src.qt.platform_strings import PlatformStrings +from ..translations import Translations + if typing.TYPE_CHECKING: from src.qt.ts_qt import QtDriver @@ -115,7 +117,8 @@ def __init__(self, driver: "QtDriver") -> None: self.setContextMenuPolicy(Qt.ContextMenuPolicy.ActionsContextMenu) self.opener = FileOpenerHelper(filepath=self.filepath) - autoplay_action = QAction("Autoplay", self) # TODO translate + autoplay_action = QAction(self) + Translations.translate_qobject(autoplay_action, "video_player.autoplay") autoplay_action.setCheckable(True) self.addAction(autoplay_action) autoplay_action.setChecked( @@ -124,7 +127,8 @@ def __init__(self, driver: "QtDriver") -> None: autoplay_action.triggered.connect(lambda: self.toggle_autoplay()) self.autoplay = autoplay_action - open_file_action = QAction("Open file", self) # TODO translate + open_file_action = QAction(self) + Translations.translate_qobject(open_file_action, "file.open_file") open_file_action.triggered.connect(self.opener.open_file) open_explorer_action = QAction(PlatformStrings.open_file_str, self) From 399e4eb5fa3f0806f26e8c931360e55c03bb3992 Mon Sep 17 00:00:00 2001 From: Jann Stute Date: Mon, 23 Dec 2024 21:38:53 +0100 Subject: [PATCH 12/26] fix: replace debug changes with simpler one --- tagstudio/src/qt/translations.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tagstudio/src/qt/translations.py b/tagstudio/src/qt/translations.py index 553cf5627..40adb9eb3 100644 --- a/tagstudio/src/qt/translations.py +++ b/tagstudio/src/qt/translations.py @@ -8,7 +8,7 @@ from .helpers.qbutton_wrapper import QPushButtonWrapper -DEFAULT_TRANSLATION = "de" +DEFAULT_TRANSLATION = "en" class TranslatedString(QObject): @@ -20,7 +20,6 @@ class TranslatedString(QObject): def __init__(self, value: str): super().__init__() self.__default_value = value - self.__value = self.__default_value # TODO remove this line @property def value(self) -> str: @@ -29,7 +28,7 @@ def value(self) -> str: @value.setter def value(self, value: str): if self.__value != value: - self.__value = value or "Not Translated" # TODO remove `or "Not Translated"` + self.__value = value self.changed.emit(self.__value) @@ -75,7 +74,7 @@ def translate_formatted(self, key: str, **kwargs) -> str: return self[key].format(**kwargs) def __getitem__(self, key: str) -> str: - return self._strings[key].value if key in self._strings else "Not Translated" + return "???" # self._strings[key].value if key in self._strings else "Not Translated" Translations = Translator() From 2889cf7631c1b992ae907cbc80fee19f24241bf6 Mon Sep 17 00:00:00 2001 From: Jann Stute Date: Mon, 23 Dec 2024 21:49:44 +0100 Subject: [PATCH 13/26] uncomment debug change --- tagstudio/src/qt/translations.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tagstudio/src/qt/translations.py b/tagstudio/src/qt/translations.py index 40adb9eb3..d03f8bda0 100644 --- a/tagstudio/src/qt/translations.py +++ b/tagstudio/src/qt/translations.py @@ -74,7 +74,8 @@ def translate_formatted(self, key: str, **kwargs) -> str: return self[key].format(**kwargs) def __getitem__(self, key: str) -> str: - return "???" # self._strings[key].value if key in self._strings else "Not Translated" + # return "???" + return self._strings[key].value if key in self._strings else "Not Translated" Translations = Translator() From a555286f46319784b443f7ae730753583ba65fac Mon Sep 17 00:00:00 2001 From: Jann Stute Date: Mon, 23 Dec 2024 21:53:33 +0100 Subject: [PATCH 14/26] fix: missing parameter in call --- tagstudio/src/qt/translations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tagstudio/src/qt/translations.py b/tagstudio/src/qt/translations.py index d03f8bda0..c1fd5ad20 100644 --- a/tagstudio/src/qt/translations.py +++ b/tagstudio/src/qt/translations.py @@ -68,7 +68,7 @@ def set_text(text: str): if key in self._strings: self._strings[key].changed.connect(set_text) - set_text(self.translate_formatted(key)) + set_text(self.translate_formatted(key, **kwargs)) def translate_formatted(self, key: str, **kwargs) -> str: return self[key].format(**kwargs) From ba43c4269a83c19162dfbff35fbb2af6e7689bac Mon Sep 17 00:00:00 2001 From: Jann Stute Date: Tue, 24 Dec 2024 14:23:16 +0100 Subject: [PATCH 15/26] fix: various review feedback --- tagstudio/resources/translations/en.json | 12 ++++-------- tagstudio/src/qt/modals/tag_database.py | 2 +- tagstudio/src/qt/platform_strings.py | 6 +++--- tagstudio/src/qt/ts_qt.py | 3 +-- tagstudio/src/qt/widgets/video_player.py | 2 +- 5 files changed, 10 insertions(+), 15 deletions(-) diff --git a/tagstudio/resources/translations/en.json b/tagstudio/resources/translations/en.json index 71fdfb6af..d0c3a8993 100644 --- a/tagstudio/resources/translations/en.json +++ b/tagstudio/resources/translations/en.json @@ -50,12 +50,9 @@ "file.not_found": "File Not Found", "file.open_file_with": "Open file with", "file.open_file": "Open file", - "file.open_file.windows": "Open file in Explorer", - "file.open_file.macos": "Open file in Finder", - "file.open_file.other": "Open file in file explorer", - "file.open_location.generic": "Show file in explorer", + "file.open_location.generic": "Show file in file explorer", "file.open_location.mac": "Reveal in Finder", - "file.open_location.windows": "Show in File Explorer", + "file.open_location.windows": "Show in Explorer", "folders_to_tags.close_all": "Close All", "folders_to_tags.converting": "Converting folders to Tags", "folders_to_tags.description": "Creates tags based on your folder structure and applies them to your entries.\n The structure below shows all the tags that will be created and what entries they will be applied to.", @@ -131,7 +128,6 @@ "menu.file.refresh_directories": "&Refresh Directories", "menu.file": "&File", "menu.help": "&Help", - "menu.macros.autofill": "Autofill", "menu.macros.folders_to_tags": "Folders to Tags", "menu.macros": "&Macros", "menu.select": "Select", @@ -172,6 +168,7 @@ "tag.search_for_tag": "Search for Tag", "tag.shorthand": "Shorthand", "tag.tag_name_required": "Tag Name (Required)", + "tag.confirm_delete": "Are you sure you want to delete the tag \"{tag_name}\"?", "view.size.0": "Mini", "view.size.1": "Small", "view.size.2": "Medium", @@ -180,7 +177,6 @@ "window.title.error": "Error", "window.title.open_create_library": "Open/Create Library", "window.message.error_opening_library": "Error opening library.", - "tag_database.confirmation": "Are you sure you want to delete the tag \"{tag_name}\"?", "drop_import.title": "Conflicting File(s)", "drop_import.decription": "The following files have filenames already exist in the library", "drop_import.progress.label.initial": "Importing New Files...", @@ -190,7 +186,7 @@ "drop_import.duplicates_choice.singular": "The following file has a filename that already exists in the library.", "drop_import.duplicates_choice.plural": "The following {count} files have filenames that already exist in the library.", "landing.open_create_library": "Open/Create Library {shortcut}", - "video_player.autoplay": "Autoplay", + "media_player.autoplay": "Autoplay", "json_migration.title": "Save Format Migration: \"{path}\"", "json_migration.info.description": "Library save files created with TagStudio versions 9.4 and below will need to be migrated to the new v9.5+ format.

What you need to know:

  • Your existing library save file will NOT be deleted
  • Your personal files will NOT be deleted, moved, or modified
  • The new v9.5+ save format can not be opened in earlier versions of TagStudio
", "json_migration.title.old_lib": "

v9.4 Library

", diff --git a/tagstudio/src/qt/modals/tag_database.py b/tagstudio/src/qt/modals/tag_database.py index 3e24a6c98..b1fbde8e5 100644 --- a/tagstudio/src/qt/modals/tag_database.py +++ b/tagstudio/src/qt/modals/tag_database.py @@ -137,7 +137,7 @@ def remove_tag(self, tag: Tag): message_box = QMessageBox() Translations.translate_with_setter(message_box.setWindowTitle, "tag.remove") - Translations.translate_qobject(message_box, "tag_database.confirmation", tag_name=tag.name) + Translations.translate_qobject(message_box, "tag.confirm_delete", tag_name=tag.name) message_box.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel) # type: ignore message_box.setIcon(QMessageBox.Question) # type: ignore diff --git a/tagstudio/src/qt/platform_strings.py b/tagstudio/src/qt/platform_strings.py index ae2339ddb..0c586cb4c 100644 --- a/tagstudio/src/qt/platform_strings.py +++ b/tagstudio/src/qt/platform_strings.py @@ -10,9 +10,9 @@ class PlatformStrings: - open_file_str: str = Translations["file.open_file.other"] + open_file_str: str = Translations["file.open_location.generic"] if platform.system() == "Windows": - open_file_str = Translations["file.open_file.windows"] + open_file_str = Translations["file.open_location.windows"] elif platform.system() == "Darwin": - open_file_str = Translations["file.open_file.macos"] + open_file_str = Translations["file.open_location.mac"] diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index 530eda3f1..880f9aaaa 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -438,8 +438,7 @@ def create_dupe_files_modal(): # tools_menu.addAction(create_collage_action) # Macros Menu ========================================================== - self.autofill_action = QAction(menu_bar) - Translations.translate_qobject(self.autofill_action, "menu.macros.autofill") + self.autofill_action = QAction("Autofill", menu_bar) self.autofill_action.triggered.connect( lambda: ( self.run_macros(MacroID.AUTOFILL, self.selected), diff --git a/tagstudio/src/qt/widgets/video_player.py b/tagstudio/src/qt/widgets/video_player.py index 187140e97..b1d4be346 100644 --- a/tagstudio/src/qt/widgets/video_player.py +++ b/tagstudio/src/qt/widgets/video_player.py @@ -118,7 +118,7 @@ def __init__(self, driver: "QtDriver") -> None: self.setContextMenuPolicy(Qt.ContextMenuPolicy.ActionsContextMenu) self.opener = FileOpenerHelper(filepath=self.filepath) autoplay_action = QAction(self) - Translations.translate_qobject(autoplay_action, "video_player.autoplay") + Translations.translate_qobject(autoplay_action, "media_player.autoplay") autoplay_action.setCheckable(True) self.addAction(autoplay_action) autoplay_action.setChecked( From 68fcce12edfbe238aaaf43de2aa9a02e86136ae8 Mon Sep 17 00:00:00 2001 From: Jann Stute Date: Tue, 24 Dec 2024 14:29:23 +0100 Subject: [PATCH 16/26] fix: don't translate json migration discrepancies list --- tagstudio/resources/translations/en.json | 6 ---- tagstudio/src/qt/widgets/migration_modal.py | 35 ++++----------------- 2 files changed, 6 insertions(+), 35 deletions(-) diff --git a/tagstudio/resources/translations/en.json b/tagstudio/resources/translations/en.json index d0c3a8993..df14ee001 100644 --- a/tagstudio/resources/translations/en.json +++ b/tagstudio/resources/translations/en.json @@ -200,12 +200,6 @@ "json_migration.migration_complete_with_discrepancies": "Migration Complete, Discrepancies Found", "json_migration.discrepancies_found": "Library Discrepancies Found", "json_migration.discrepancies_found.description": "Discrepancies were found between the original and converted library formats. Please review and choose to whether continue with the migration or to cancel.", - "json_migration.discrepancies.sql_entry_id_missing": "[Field Comparison]:\nNEW (SQL): SQL Entry ID not found: {id}", - "json_migration.discrepancies.fields": "[Field Comparison]:\nOLD (JSON):{json_fields}\nNEW (SQL):{sql_fields}", - "json_migration.discrepancies.subtag": "[Subtag Parity]:\nOLD (JSON):{json_subtags}\nNEW (SQL):{sql_subtags}", - "json_migration.discrepancies.alias": "[Alias Parity]:\nOLD (JSON):{json_aliases}\nNEW (SQL):{sql_aliases}", - "json_migration.discrepancies.shorthand": "[Shorthand Parity]:\nOLD (JSON):{json_shorthand}\nNEW (SQL):{sql_shorthand}", - "json_migration.discrepancies.color": "[Color Parity]:\nOLD (JSON):{json_color}\nNEW (SQL):{sql_color}", "json_migration.heading.match": "Matched", "json_migration.heading.differ": "Discrepancy", "json_migration.heading.entires": "Entries:", diff --git a/tagstudio/src/qt/widgets/migration_modal.py b/tagstudio/src/qt/widgets/migration_modal.py index eeb5d4d26..58f60262f 100644 --- a/tagstudio/src/qt/widgets/migration_modal.py +++ b/tagstudio/src/qt/widgets/migration_modal.py @@ -542,10 +542,7 @@ def sanitize_json_field(value): message=f"NEW (SQL): SQL Entry ID mismatch: {json_entry.id+1}", ) self.discrepancies.append( - Translations.translate_formatted( - "json_migration.discrepancies.sql_entry_id_missing", - id=json_entry.id + 1, - ) + f"[Field Comparison]:\nNEW (SQL): SQL Entry ID not found: {json_entry.id+1}" ) self.field_parity = False return self.field_parity @@ -623,11 +620,7 @@ def sanitize_json_field(value): and (json_fields == sql_fields) ): self.discrepancies.append( - Translations.translate_formatted( - "json_migration.discrepancies.fields", - json_fields=json_fields, - sql_fields=sql_fields, - ) + f"[Field Comparison]:\nOLD (JSON):{json_fields}\nNEW (SQL):{sql_fields}" ) self.field_parity = False return self.field_parity @@ -679,11 +672,7 @@ def check_subtag_parity(self) -> bool: and (sql_subtags == json_subtags) ): self.discrepancies.append( - Translations.translate_formatted( - "json_migration.discrepancies.subtag", - json_subtags=json_subtags, - sql_subtags=sql_subtags, - ) + f"[Subtag Parity]:\nOLD (JSON):{json_subtags}\nNEW (SQL):{sql_subtags}" ) self.subtag_parity = False return self.subtag_parity @@ -719,11 +708,7 @@ def check_alias_parity(self) -> bool: and (sql_aliases == json_aliases) ): self.discrepancies.append( - Translations.translate_formatted( - "json_migration.discrepancies.alias", - json_aliases=json_aliases, - sql_aliases=sql_aliases, - ) + f"[Alias Parity]:\nOLD (JSON):{json_aliases}\nNEW (SQL):{sql_aliases}" ) self.alias_parity = False return self.alias_parity @@ -754,11 +739,7 @@ def check_shorthand_parity(self) -> bool: and (sql_shorthand == json_shorthand) ): self.discrepancies.append( - Translations.translate_formatted( - "json_migration.discrepancies.shorthand", - json_shorthand=json_shorthand, - sql_shorthand=sql_shorthand, - ) + f"[Shorthand Parity]:\nOLD (JSON):{json_shorthand}\nNEW (SQL):{sql_shorthand}" ) self.shorthand_parity = False return self.shorthand_parity @@ -789,11 +770,7 @@ def check_color_parity(self) -> bool: if not (sql_color is not None and json_color is not None and (sql_color == json_color)): self.discrepancies.append( - Translations.translate_formatted( - "json_migration.discrepancies.color", - json_color=json_color, - sql_color=sql_color, - ) + f"[Color Parity]:\nOLD (JSON):{json_color}\nNEW (SQL):{sql_color}" ) self.color_parity = False return self.color_parity From a688cadee0ad97b50595f7251bfcacb69d8cecfa Mon Sep 17 00:00:00 2001 From: Jann Stute Date: Tue, 24 Dec 2024 14:44:07 +0100 Subject: [PATCH 17/26] fix: typo --- tagstudio/resources/translations/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tagstudio/resources/translations/en.json b/tagstudio/resources/translations/en.json index df14ee001..1d89b4f71 100644 --- a/tagstudio/resources/translations/en.json +++ b/tagstudio/resources/translations/en.json @@ -212,5 +212,5 @@ "json_migration.heading.extension_list_type": "Extension List Type:", "json_migration.heading.paths": "Paths:", "json_migration.heading.fields": "Fields:", - "json_migration.description": "
Start and preview the results of the library migration process. The converted library will not be used unless you click \"Finish Migration\".

Library data should either have matching values or a feature a \"Matched\" label. Values that do not match will be displayed in red and feature a \"(!)\" symbol next to them.
This process may take up to several minutes for larger libraries.
" + "json_migration.description": "
Start and preview the results of the library migration process. The converted library will not be used unless you click \"Finish Migration\".

Library data should either have matching values or feature a \"Matched\" label. Values that do not match will be displayed in red and feature a \"(!)\" symbol next to them.
This process may take up to several minutes for larger libraries.
" } \ No newline at end of file From f37833d6ac282020960679441ac6dcc559324e9e Mon Sep 17 00:00:00 2001 From: Jann Stute Date: Tue, 24 Dec 2024 14:58:45 +0100 Subject: [PATCH 18/26] fix: various PR feedback --- tagstudio/resources/translations/en.json | 2 +- tagstudio/src/qt/main_window.py | 2 +- tagstudio/src/qt/modals/add_field.py | 3 +-- tagstudio/src/qt/modals/build_tag.py | 3 +-- tagstudio/src/qt/modals/delete_unlinked.py | 3 +-- tagstudio/src/qt/modals/drop_import.py | 3 +-- tagstudio/src/qt/modals/file_extension.py | 5 ++--- tagstudio/src/qt/modals/fix_dupes.py | 3 +-- tagstudio/src/qt/modals/fix_unlinked.py | 3 +-- tagstudio/src/qt/modals/folders_to_tags.py | 3 +-- tagstudio/src/qt/modals/merge_dupe_entries.py | 3 +-- tagstudio/src/qt/modals/mirror_entities.py | 3 +-- tagstudio/src/qt/modals/relink_unlinked.py | 3 +-- tagstudio/src/qt/modals/tag_database.py | 3 +-- tagstudio/src/qt/modals/tag_search.py | 3 +-- tagstudio/src/qt/platform_strings.py | 2 +- tagstudio/src/qt/translations.py | 1 + tagstudio/src/qt/ts_qt.py | 3 +-- tagstudio/src/qt/widgets/item_thumb.py | 3 +-- tagstudio/src/qt/widgets/landing.py | 3 +-- tagstudio/src/qt/widgets/migration_modal.py | 3 +-- tagstudio/src/qt/widgets/panel.py | 3 +-- tagstudio/src/qt/widgets/preview_panel.py | 3 +-- tagstudio/src/qt/widgets/tag.py | 3 +-- tagstudio/src/qt/widgets/tag_box.py | 3 +-- tagstudio/src/qt/widgets/video_player.py | 3 +-- 26 files changed, 27 insertions(+), 48 deletions(-) diff --git a/tagstudio/resources/translations/en.json b/tagstudio/resources/translations/en.json index 1d89b4f71..442bf045a 100644 --- a/tagstudio/resources/translations/en.json +++ b/tagstudio/resources/translations/en.json @@ -18,7 +18,7 @@ "entries.unlinked.delete.deleting": "Deleting Entries", "entries.unlinked.delete": "Delete Unlinked Entries", "entries.unlinked.delete_alt": "De&lete Unlinked Entries", - "entries.unlinked.description": "Each library entry is linked to a file in one of your directories. If a file linked to an entry is moved or deleted outside of TagStudio, it is then considered unlinked. Unlinked entries may be automatically relinked via searching your directories or deleted if desired.", + "entries.unlinked.description": "Each library entry is linked to a file in one of your directories. If a file linked to an entry is moved or deleted outside of TagStudio, it is then considered unlinked.

Unlinked entries may be automatically relinked via searching your directories or deleted if desired.", "entries.unlinked.refresh_all": "&Refresh All", "entries.unlinked.relink.attempting": "Attempting to Relink {idx}/{missing_count} Entries, {fixed_count} Successfully Relinked", "entries.unlinked.relink.manual": "&Manual Relink", diff --git a/tagstudio/src/qt/main_window.py b/tagstudio/src/qt/main_window.py index 77bf338ab..d1125edeb 100644 --- a/tagstudio/src/qt/main_window.py +++ b/tagstudio/src/qt/main_window.py @@ -25,7 +25,7 @@ from src.qt.pagination import Pagination from src.qt.widgets.landing import LandingWidget -from .translations import Translations +from src.qt.translations import Translations # Only import for type checking/autocompletion, will not be imported at runtime. if typing.TYPE_CHECKING: diff --git a/tagstudio/src/qt/modals/add_field.py b/tagstudio/src/qt/modals/add_field.py index 3e2a94b8b..e948e5116 100644 --- a/tagstudio/src/qt/modals/add_field.py +++ b/tagstudio/src/qt/modals/add_field.py @@ -14,8 +14,7 @@ QWidget, ) from src.core.library import Library - -from ..translations import Translations +from src.qt.translations import Translations class AddFieldModal(QWidget): diff --git a/tagstudio/src/qt/modals/build_tag.py b/tagstudio/src/qt/modals/build_tag.py index 73687e384..5682c7360 100644 --- a/tagstudio/src/qt/modals/build_tag.py +++ b/tagstudio/src/qt/modals/build_tag.py @@ -24,11 +24,10 @@ from src.core.library.alchemy.enums import TagColor from src.core.palette import ColorType, UiColor, get_tag_color, get_ui_color from src.qt.modals.tag_search import TagSearchPanel +from src.qt.translations import Translations from src.qt.widgets.panel import PanelModal, PanelWidget from src.qt.widgets.tag import TagWidget -from ..translations import Translations - logger = structlog.get_logger(__name__) diff --git a/tagstudio/src/qt/modals/delete_unlinked.py b/tagstudio/src/qt/modals/delete_unlinked.py index 180c6e75b..da229c994 100644 --- a/tagstudio/src/qt/modals/delete_unlinked.py +++ b/tagstudio/src/qt/modals/delete_unlinked.py @@ -15,10 +15,9 @@ QWidget, ) from src.core.utils.missing_files import MissingRegistry +from src.qt.translations import Translations from src.qt.widgets.progress import ProgressWidget -from ..translations import Translations - # Only import for type checking/autocompletion, will not be imported at runtime. if typing.TYPE_CHECKING: from src.qt.ts_qt import QtDriver diff --git a/tagstudio/src/qt/modals/drop_import.py b/tagstudio/src/qt/modals/drop_import.py index 7811a46de..92b9f1672 100644 --- a/tagstudio/src/qt/modals/drop_import.py +++ b/tagstudio/src/qt/modals/drop_import.py @@ -17,10 +17,9 @@ QVBoxLayout, QWidget, ) +from src.qt.translations import Translations from src.qt.widgets.progress import ProgressWidget -from ..translations import Translations - if TYPE_CHECKING: from src.qt.ts_qt import QtDriver diff --git a/tagstudio/src/qt/modals/file_extension.py b/tagstudio/src/qt/modals/file_extension.py index 45ea01990..49b5b1478 100644 --- a/tagstudio/src/qt/modals/file_extension.py +++ b/tagstudio/src/qt/modals/file_extension.py @@ -18,10 +18,9 @@ ) from src.core.enums import LibraryPrefs from src.core.library import Library +from src.qt.translations import Translations from src.qt.widgets.panel import PanelWidget -from ..translations import Translations - class FileExtensionItemDelegate(QStyledItemDelegate): def setModelData(self, editor, model, index): # noqa: N802 @@ -72,7 +71,7 @@ def __init__(self, library: "Library"): lambda text: self.mode_combobox.setItemText(0, text), "ignore_list.mode.include" ) Translations.translate_with_setter( - lambda text: self.mode_combobox.setItemText(0, text), "ignore_list.mode.exclude" + lambda text: self.mode_combobox.setItemText(1, text), "ignore_list.mode.exclude" ) is_exclude_list = int(bool(self.lib.prefs(LibraryPrefs.IS_EXCLUDE_LIST))) diff --git a/tagstudio/src/qt/modals/fix_dupes.py b/tagstudio/src/qt/modals/fix_dupes.py index 12095d9fb..762610399 100644 --- a/tagstudio/src/qt/modals/fix_dupes.py +++ b/tagstudio/src/qt/modals/fix_dupes.py @@ -17,8 +17,7 @@ from src.core.library import Library from src.core.utils.dupe_files import DupeRegistry from src.qt.modals.mirror_entities import MirrorEntriesModal - -from ..translations import Translations +from src.qt.translations import Translations # Only import for type checking/autocompletion, will not be imported at runtime. if typing.TYPE_CHECKING: diff --git a/tagstudio/src/qt/modals/fix_unlinked.py b/tagstudio/src/qt/modals/fix_unlinked.py index 3226566f1..eafe3297a 100644 --- a/tagstudio/src/qt/modals/fix_unlinked.py +++ b/tagstudio/src/qt/modals/fix_unlinked.py @@ -12,10 +12,9 @@ from src.qt.modals.delete_unlinked import DeleteUnlinkedEntriesModal from src.qt.modals.merge_dupe_entries import MergeDuplicateEntries from src.qt.modals.relink_unlinked import RelinkUnlinkedEntries +from src.qt.translations import Translations from src.qt.widgets.progress import ProgressWidget -from ..translations import Translations - # Only import for type checking/autocompletion, will not be imported at runtime. if typing.TYPE_CHECKING: from src.qt.ts_qt import QtDriver diff --git a/tagstudio/src/qt/modals/folders_to_tags.py b/tagstudio/src/qt/modals/folders_to_tags.py index 60fcd3187..f680fdc18 100644 --- a/tagstudio/src/qt/modals/folders_to_tags.py +++ b/tagstudio/src/qt/modals/folders_to_tags.py @@ -23,8 +23,7 @@ from src.core.library.alchemy.fields import _FieldID from src.core.palette import ColorType, get_tag_color from src.qt.flowlayout import FlowLayout - -from ..translations import Translations +from src.qt.translations import Translations if typing.TYPE_CHECKING: from src.qt.ts_qt import QtDriver diff --git a/tagstudio/src/qt/modals/merge_dupe_entries.py b/tagstudio/src/qt/modals/merge_dupe_entries.py index 5d2d49af9..35bdf7c15 100644 --- a/tagstudio/src/qt/modals/merge_dupe_entries.py +++ b/tagstudio/src/qt/modals/merge_dupe_entries.py @@ -7,10 +7,9 @@ from PySide6.QtCore import QObject, Signal from src.core.library import Library from src.core.utils.dupe_files import DupeRegistry +from src.qt.translations import Translations from src.qt.widgets.progress import ProgressWidget -from ..translations import Translations - # Only import for type checking/autocompletion, will not be imported at runtime. if typing.TYPE_CHECKING: from src.qt.ts_qt import QtDriver diff --git a/tagstudio/src/qt/modals/mirror_entities.py b/tagstudio/src/qt/modals/mirror_entities.py index c872fa58e..4cb37a1cf 100644 --- a/tagstudio/src/qt/modals/mirror_entities.py +++ b/tagstudio/src/qt/modals/mirror_entities.py @@ -17,10 +17,9 @@ QWidget, ) from src.core.utils.dupe_files import DupeRegistry +from src.qt.translations import Translations from src.qt.widgets.progress import ProgressWidget -from ..translations import Translations - # Only import for type checking/autocompletion, will not be imported at runtime. if typing.TYPE_CHECKING: from src.qt.ts_qt import QtDriver diff --git a/tagstudio/src/qt/modals/relink_unlinked.py b/tagstudio/src/qt/modals/relink_unlinked.py index 366930a75..0363c8486 100644 --- a/tagstudio/src/qt/modals/relink_unlinked.py +++ b/tagstudio/src/qt/modals/relink_unlinked.py @@ -5,10 +5,9 @@ from PySide6.QtCore import QObject, Signal from src.core.utils.missing_files import MissingRegistry +from src.qt.translations import Translations from src.qt.widgets.progress import ProgressWidget -from ..translations import Translations - class RelinkUnlinkedEntries(QObject): done = Signal() diff --git a/tagstudio/src/qt/modals/tag_database.py b/tagstudio/src/qt/modals/tag_database.py index b1fbde8e5..560611d51 100644 --- a/tagstudio/src/qt/modals/tag_database.py +++ b/tagstudio/src/qt/modals/tag_database.py @@ -18,11 +18,10 @@ from src.core.constants import RESERVED_TAG_END, RESERVED_TAG_START from src.core.library import Library, Tag from src.qt.modals.build_tag import BuildTagPanel +from src.qt.translations import Translations from src.qt.widgets.panel import PanelModal, PanelWidget from src.qt.widgets.tag import TagWidget -from ..translations import Translations - logger = structlog.get_logger(__name__) # TODO: This class shares the majority of its code with tag_search.py. diff --git a/tagstudio/src/qt/modals/tag_search.py b/tagstudio/src/qt/modals/tag_search.py index 63cac736e..90932b0de 100644 --- a/tagstudio/src/qt/modals/tag_search.py +++ b/tagstudio/src/qt/modals/tag_search.py @@ -19,11 +19,10 @@ ) from src.core.library import Library from src.core.palette import ColorType, get_tag_color +from src.qt.translations import Translations from src.qt.widgets.panel import PanelWidget from src.qt.widgets.tag import TagWidget -from ..translations import Translations - logger = structlog.get_logger(__name__) diff --git a/tagstudio/src/qt/platform_strings.py b/tagstudio/src/qt/platform_strings.py index 0c586cb4c..23851dd48 100644 --- a/tagstudio/src/qt/platform_strings.py +++ b/tagstudio/src/qt/platform_strings.py @@ -6,7 +6,7 @@ import platform -from .translations import Translations +from src.qt.translations import Translations class PlatformStrings: diff --git a/tagstudio/src/qt/translations.py b/tagstudio/src/qt/translations.py index c1fd5ad20..72e232b8a 100644 --- a/tagstudio/src/qt/translations.py +++ b/tagstudio/src/qt/translations.py @@ -79,3 +79,4 @@ def __getitem__(self, key: str) -> str: Translations = Translator() +# Translations.change_language("de") diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index 880f9aaaa..06c136dc4 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -84,6 +84,7 @@ from src.qt.modals.folders_to_tags import FoldersToTagsModal from src.qt.modals.tag_database import TagDatabasePanel from src.qt.resource_manager import ResourceManager +from src.qt.translations import Translations from src.qt.widgets.item_thumb import BadgeType, ItemThumb from src.qt.widgets.migration_modal import JsonMigrationModal from src.qt.widgets.panel import PanelModal @@ -91,8 +92,6 @@ from src.qt.widgets.progress import ProgressWidget from src.qt.widgets.thumb_renderer import ThumbRenderer -from .translations import Translations - # SIGQUIT is not defined on Windows if sys.platform == "win32": from signal import SIGINT, SIGTERM, signal diff --git a/tagstudio/src/qt/widgets/item_thumb.py b/tagstudio/src/qt/widgets/item_thumb.py index 78a644b93..f79708ad4 100644 --- a/tagstudio/src/qt/widgets/item_thumb.py +++ b/tagstudio/src/qt/widgets/item_thumb.py @@ -30,11 +30,10 @@ from src.qt.flowlayout import FlowWidget from src.qt.helpers.file_opener import FileOpenerHelper from src.qt.platform_strings import PlatformStrings +from src.qt.translations import Translations from src.qt.widgets.thumb_button import ThumbButton from src.qt.widgets.thumb_renderer import ThumbRenderer -from ..translations import Translations - if TYPE_CHECKING: from src.qt.ts_qt import QtDriver diff --git a/tagstudio/src/qt/widgets/landing.py b/tagstudio/src/qt/widgets/landing.py index 6f3b6e433..3f6b15942 100644 --- a/tagstudio/src/qt/widgets/landing.py +++ b/tagstudio/src/qt/widgets/landing.py @@ -13,10 +13,9 @@ from PySide6.QtGui import QPixmap from PySide6.QtWidgets import QLabel, QPushButton, QVBoxLayout, QWidget from src.qt.helpers.color_overlay import gradient_overlay, theme_fg_overlay +from src.qt.translations import Translations from src.qt.widgets.clickable_label import ClickableLabel -from ..translations import Translations - # Only import for type checking/autocompletion, will not be imported at runtime. if typing.TYPE_CHECKING: from src.qt.ts_qt import QtDriver diff --git a/tagstudio/src/qt/widgets/migration_modal.py b/tagstudio/src/qt/widgets/migration_modal.py index 58f60262f..6e8aeb06b 100644 --- a/tagstudio/src/qt/widgets/migration_modal.py +++ b/tagstudio/src/qt/widgets/migration_modal.py @@ -30,12 +30,11 @@ from src.qt.helpers.custom_runnable import CustomRunnable from src.qt.helpers.function_iterator import FunctionIterator from src.qt.helpers.qbutton_wrapper import QPushButtonWrapper +from src.qt.translations import Translations from src.qt.widgets.paged_panel.paged_body_wrapper import PagedBodyWrapper from src.qt.widgets.paged_panel.paged_panel import PagedPanel from src.qt.widgets.paged_panel.paged_panel_state import PagedPanelState -from ..translations import Translations - logger = structlog.get_logger(__name__) diff --git a/tagstudio/src/qt/widgets/panel.py b/tagstudio/src/qt/widgets/panel.py index 8f11a5be7..573f6f48d 100755 --- a/tagstudio/src/qt/widgets/panel.py +++ b/tagstudio/src/qt/widgets/panel.py @@ -6,8 +6,7 @@ from PySide6.QtCore import Qt, Signal from PySide6.QtWidgets import QHBoxLayout, QLabel, QPushButton, QVBoxLayout, QWidget - -from ..translations import Translations +from src.qt.translations import Translations class PanelModal(QWidget): diff --git a/tagstudio/src/qt/widgets/preview_panel.py b/tagstudio/src/qt/widgets/preview_panel.py index 051ed28e5..0bb1c1812 100644 --- a/tagstudio/src/qt/widgets/preview_panel.py +++ b/tagstudio/src/qt/widgets/preview_panel.py @@ -52,6 +52,7 @@ from src.qt.helpers.rounded_pixmap_style import RoundedPixmapStyle from src.qt.modals.add_field import AddFieldModal from src.qt.platform_strings import PlatformStrings +from src.qt.translations import Translations from src.qt.widgets.fields import FieldContainer from src.qt.widgets.media_player import MediaPlayer from src.qt.widgets.panel import PanelModal @@ -62,8 +63,6 @@ from src.qt.widgets.thumb_renderer import ThumbRenderer from src.qt.widgets.video_player import VideoPlayer -from ..translations import Translations - if typing.TYPE_CHECKING: from src.qt.ts_qt import QtDriver diff --git a/tagstudio/src/qt/widgets/tag.py b/tagstudio/src/qt/widgets/tag.py index 7cd7f697b..46f5fb9bb 100644 --- a/tagstudio/src/qt/widgets/tag.py +++ b/tagstudio/src/qt/widgets/tag.py @@ -20,8 +20,7 @@ from src.core.library import Tag from src.core.library.alchemy.enums import TagColor from src.core.palette import ColorType, get_tag_color - -from ..translations import Translations +from src.qt.translations import Translations class TagAliasWidget(QWidget): diff --git a/tagstudio/src/qt/widgets/tag_box.py b/tagstudio/src/qt/widgets/tag_box.py index 26f666a6e..8e095d79a 100755 --- a/tagstudio/src/qt/widgets/tag_box.py +++ b/tagstudio/src/qt/widgets/tag_box.py @@ -16,12 +16,11 @@ from src.qt.flowlayout import FlowLayout from src.qt.modals.build_tag import BuildTagPanel from src.qt.modals.tag_search import TagSearchPanel +from src.qt.translations import Translations from src.qt.widgets.fields import FieldWidget from src.qt.widgets.panel import PanelModal from src.qt.widgets.tag import TagWidget -from ..translations import Translations - if typing.TYPE_CHECKING: from src.qt.ts_qt import QtDriver diff --git a/tagstudio/src/qt/widgets/video_player.py b/tagstudio/src/qt/widgets/video_player.py index b1d4be346..7e5e6ba15 100644 --- a/tagstudio/src/qt/widgets/video_player.py +++ b/tagstudio/src/qt/widgets/video_player.py @@ -31,8 +31,7 @@ from src.core.enums import SettingItems from src.qt.helpers.file_opener import FileOpenerHelper from src.qt.platform_strings import PlatformStrings - -from ..translations import Translations +from src.qt.translations import Translations if typing.TYPE_CHECKING: from src.qt.ts_qt import QtDriver From cf48d6a711cd16d7d5bdffb4ad64a46476307270 Mon Sep 17 00:00:00 2001 From: Jann Stute Date: Wed, 25 Dec 2024 01:04:55 +0100 Subject: [PATCH 19/26] fix: correctly read non-ascii characters --- tagstudio/src/qt/translations.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tagstudio/src/qt/translations.py b/tagstudio/src/qt/translations.py index 72e232b8a..1d848a0a7 100644 --- a/tagstudio/src/qt/translations.py +++ b/tagstudio/src/qt/translations.py @@ -40,7 +40,10 @@ def __init__(self): self._strings[k] = TranslatedString(v) def __get_translation_dict(self, lang: str) -> dict[str, str]: - with open(Path(__file__).parents[2] / "resources" / "translations" / f"{lang}.json") as f: + with open( + Path(__file__).parents[2] / "resources" / "translations" / f"{lang}.json", + encoding="utf-8", + ) as f: return ujson.loads(f.read()) def change_language(self, lang: str): @@ -79,4 +82,4 @@ def __getitem__(self, key: str) -> str: Translations = Translator() -# Translations.change_language("de") +Translations.change_language("de") From 0f44af927f8e7a68dfbacd0c1605e7116ea37c48 Mon Sep 17 00:00:00 2001 From: Jann Stute <46534683+Computerdores@users.noreply.github.com> Date: Sun, 29 Dec 2024 21:21:02 +0000 Subject: [PATCH 20/26] fix: add missing new line at eof --- tagstudio/resources/translations/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tagstudio/resources/translations/en.json b/tagstudio/resources/translations/en.json index 442bf045a..73a3edfb1 100644 --- a/tagstudio/resources/translations/en.json +++ b/tagstudio/resources/translations/en.json @@ -213,4 +213,4 @@ "json_migration.heading.paths": "Paths:", "json_migration.heading.fields": "Fields:", "json_migration.description": "
Start and preview the results of the library migration process. The converted library will not be used unless you click \"Finish Migration\".

Library data should either have matching values or feature a \"Matched\" label. Values that do not match will be displayed in red and feature a \"(!)\" symbol next to them.
This process may take up to several minutes for larger libraries.
" -} \ No newline at end of file +} From d594e8496c082c0982ae9aabdff151aff940772c Mon Sep 17 00:00:00 2001 From: Jann Stute Date: Sun, 29 Dec 2024 22:32:30 +0100 Subject: [PATCH 21/26] fix: comment out line of debug code --- tagstudio/src/qt/translations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tagstudio/src/qt/translations.py b/tagstudio/src/qt/translations.py index 1d848a0a7..88c002ee6 100644 --- a/tagstudio/src/qt/translations.py +++ b/tagstudio/src/qt/translations.py @@ -82,4 +82,4 @@ def __getitem__(self, key: str) -> str: Translations = Translator() -Translations.change_language("de") +# Translations.change_language("de") From 465bc41ddf46acfa484218be3412516074819d76 Mon Sep 17 00:00:00 2001 From: Jann Stute Date: Sun, 29 Dec 2024 22:42:30 +0100 Subject: [PATCH 22/26] fix: address latest review comment --- tagstudio/resources/translations/en.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tagstudio/resources/translations/en.json b/tagstudio/resources/translations/en.json index 73a3edfb1..d70b53215 100644 --- a/tagstudio/resources/translations/en.json +++ b/tagstudio/resources/translations/en.json @@ -52,7 +52,7 @@ "file.open_file": "Open file", "file.open_location.generic": "Show file in file explorer", "file.open_location.mac": "Reveal in Finder", - "file.open_location.windows": "Show in Explorer", + "file.open_location.windows": "Show in File Explorer", "folders_to_tags.close_all": "Close All", "folders_to_tags.converting": "Converting folders to Tags", "folders_to_tags.description": "Creates tags based on your folder structure and applies them to your entries.\n The structure below shows all the tags that will be created and what entries they will be applied to.", @@ -213,4 +213,4 @@ "json_migration.heading.paths": "Paths:", "json_migration.heading.fields": "Fields:", "json_migration.description": "
Start and preview the results of the library migration process. The converted library will not be used unless you click \"Finish Migration\".

Library data should either have matching values or feature a \"Matched\" label. Values that do not match will be displayed in red and feature a \"(!)\" symbol next to them.
This process may take up to several minutes for larger libraries.
" -} +} \ No newline at end of file From cc4b6dffc55547c4108c55e9a19e5afc2ef64449 Mon Sep 17 00:00:00 2001 From: Jann Stute Date: Sun, 29 Dec 2024 23:51:12 +0100 Subject: [PATCH 23/26] fix: KeyError that occurred when formatting translations --- tagstudio/src/qt/translations.py | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/tagstudio/src/qt/translations.py b/tagstudio/src/qt/translations.py index 88c002ee6..e97633b4d 100644 --- a/tagstudio/src/qt/translations.py +++ b/tagstudio/src/qt/translations.py @@ -1,6 +1,7 @@ from pathlib import Path from typing import Callable +import structlog import ujson from PySide6.QtCore import QObject, Signal from PySide6.QtGui import QAction @@ -8,6 +9,8 @@ from .helpers.qbutton_wrapper import QPushButtonWrapper +logger = structlog.get_logger(__name__) + DEFAULT_TRANSLATION = "en" @@ -34,6 +37,7 @@ def value(self, value: str): class Translator: _strings: dict[str, TranslatedString] = {} + _lang: str = DEFAULT_TRANSLATION def __init__(self): for k, v in self.__get_translation_dict(DEFAULT_TRANSLATION).items(): @@ -47,6 +51,7 @@ def __get_translation_dict(self, lang: str) -> dict[str, str]: return ujson.loads(f.read()) def change_language(self, lang: str): + self._lang = lang translated = self.__get_translation_dict(lang) for k in self._strings: self._strings[k].value = translated.get(k, None) @@ -65,16 +70,21 @@ def translate_with_setter(self, setter: Callable[[str], None], key: str, **kwarg Also formats the translation with the given keyword arguments. """ - - def set_text(text: str): - setter(text.format(**kwargs)) - if key in self._strings: - self._strings[key].changed.connect(set_text) - set_text(self.translate_formatted(key, **kwargs)) + self._strings[key].changed.connect(lambda text: setter(self.__format(text, **kwargs))) + setter(self.translate_formatted(key, **kwargs)) + + def __format(self, text: str, **kwargs) -> str: + try: + return text.format(**kwargs) + except KeyError: + logger.warning( + "Error while formatting translation.", text=text, kwargs=kwargs, language=self._lang + ) + return text def translate_formatted(self, key: str, **kwargs) -> str: - return self[key].format(**kwargs) + return self.__format(self[key], **kwargs) def __getitem__(self, key: str) -> str: # return "???" @@ -82,4 +92,4 @@ def __getitem__(self, key: str) -> str: Translations = Translator() -# Translations.change_language("de") +Translations.change_language("de") From 73010ccf3554b01afbcadb70a4e4fc7ef5ba39c2 Mon Sep 17 00:00:00 2001 From: Jann Stute Date: Mon, 30 Dec 2024 15:43:35 +0100 Subject: [PATCH 24/26] fix: regression of d594e84 --- tagstudio/src/qt/translations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tagstudio/src/qt/translations.py b/tagstudio/src/qt/translations.py index e97633b4d..5d2dcea0a 100644 --- a/tagstudio/src/qt/translations.py +++ b/tagstudio/src/qt/translations.py @@ -92,4 +92,4 @@ def __getitem__(self, key: str) -> str: Translations = Translator() -Translations.change_language("de") +# Translations.change_language("de") From cdc5bf1d4961e5b39315a077b0165e2b06be4e11 Mon Sep 17 00:00:00 2001 From: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com> Date: Mon, 30 Dec 2024 19:05:14 -0800 Subject: [PATCH 25/26] fix: add newline to en.json --- tagstudio/resources/translations/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tagstudio/resources/translations/en.json b/tagstudio/resources/translations/en.json index d70b53215..b27f5d499 100644 --- a/tagstudio/resources/translations/en.json +++ b/tagstudio/resources/translations/en.json @@ -213,4 +213,4 @@ "json_migration.heading.paths": "Paths:", "json_migration.heading.fields": "Fields:", "json_migration.description": "
Start and preview the results of the library migration process. The converted library will not be used unless you click \"Finish Migration\".

Library data should either have matching values or feature a \"Matched\" label. Values that do not match will be displayed in red and feature a \"(!)\" symbol next to them.
This process may take up to several minutes for larger libraries.
" -} \ No newline at end of file +} From 39562e973010138253f49c81e2dca8f0f8b14e5e Mon Sep 17 00:00:00 2001 From: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com> Date: Mon, 30 Dec 2024 19:09:53 -0800 Subject: [PATCH 26/26] fix: organize en.json, fix typo --- tagstudio/resources/translations/en.json | 130 +++++++++++------------ tagstudio/src/qt/modals/drop_import.py | 2 +- 2 files changed, 66 insertions(+), 66 deletions(-) diff --git a/tagstudio/resources/translations/en.json b/tagstudio/resources/translations/en.json index b27f5d499..e9247b71e 100644 --- a/tagstudio/resources/translations/en.json +++ b/tagstudio/resources/translations/en.json @@ -2,6 +2,14 @@ "app.git": "Git Commit", "app.pre_release": "Pre-Release", "app.title": "{base_title} - Library '{library_dir}'", + "drop_import.description": "The following files have filenames already exist in the library", + "drop_import.duplicates_choice.plural": "The following {count} files have filenames that already exist in the library.", + "drop_import.duplicates_choice.singular": "The following file has a filename that already exists in the library.", + "drop_import.progress.label.initial": "Importing New Files...", + "drop_import.progress.label.plural": "Importing New Files...\n{count} Files Imported.{suffix}", + "drop_import.progress.label.singular": "Importing New Files...\n1 File imported.{suffix}", + "drop_import.progress.window_title": "Import Files", + "drop_import.title": "Conflicting File(s)", "edit.tag_manager": "Manage Tags", "entries.duplicate.merge.label": "Merging Duplicate Entries...", "entries.duplicate.merge": "Merge Duplicate Entries", @@ -13,12 +21,14 @@ "entries.mirror.window_title": "Mirror Entries", "entries.mirror": "&Mirror", "entries.tags": "Tags", + "entries.unlinked.delete_alt": "De&lete Unlinked Entries", "entries.unlinked.delete.confirm": "Are you sure you want to delete the following {count} entries?", "entries.unlinked.delete.deleting_count": "Deleting {idx}/{count} Unlinked Entries", "entries.unlinked.delete.deleting": "Deleting Entries", "entries.unlinked.delete": "Delete Unlinked Entries", - "entries.unlinked.delete_alt": "De&lete Unlinked Entries", "entries.unlinked.description": "Each library entry is linked to a file in one of your directories. If a file linked to an entry is moved or deleted outside of TagStudio, it is then considered unlinked.

Unlinked entries may be automatically relinked via searching your directories or deleted if desired.", + "entries.unlinked.missing_count.none": "Unlinked Entries: N/A", + "entries.unlinked.missing_count.some": "Unlinked Entries: {count}", "entries.unlinked.refresh_all": "&Refresh All", "entries.unlinked.relink.attempting": "Attempting to Relink {idx}/{missing_count} Entries, {fixed_count} Successfully Relinked", "entries.unlinked.relink.manual": "&Manual Relink", @@ -26,8 +36,6 @@ "entries.unlinked.scanning": "Scanning Library for Unlinked Entries...", "entries.unlinked.search_and_relink": "&Search && Relink", "entries.unlinked.title": "Fix Unlinked Entries", - "entries.unlinked.missing_count.none": "Unlinked Entries: N/A", - "entries.unlinked.missing_count.some": "Unlinked Entries: {count}", "field.copy": "Copy Field", "field.edit": "Edit Field", "field.paste": "Paste Field", @@ -59,32 +67,32 @@ "folders_to_tags.open_all": "Open All", "folders_to_tags.title": "Create Tags From Folders", "generic.add": "Add", - "generic.apply": "Apply", "generic.apply_alt": "&Apply", - "generic.cancel": "Cancel", + "generic.apply": "Apply", "generic.cancel_alt": "&Cancel", + "generic.cancel": "Cancel", "generic.close": "Close", + "generic.continue": "Continue", "generic.copy": "Copy", "generic.cut": "Cut", - "generic.save": "Save", - "generic.delete": "Delete", "generic.delete_alt": "&Delete", - "generic.continue": "Continue", - "generic.done": "Done", + "generic.delete": "Delete", "generic.done_alt": "&Done", - "generic.edit": "Edit", + "generic.done": "Done", "generic.edit_alt": "&Edit", - "generic.rename": "Rename", - "generic.rename_alt": "&Rename", - "generic.overwrite": "Overwrite", - "generic.overwrite_alt": "&Overwrite", - "generic.skip": "Skip", - "generic.skip_alt": "&Skip", + "generic.edit": "Edit", + "generic.filename": "Filename", "generic.navigation.back": "Back", "generic.navigation.next": "Next", + "generic.overwrite_alt": "&Overwrite", + "generic.overwrite": "Overwrite", "generic.paste": "Paste", "generic.recent_libraries": "Recent Libraries", - "generic.filename": "Filename", + "generic.rename_alt": "&Rename", + "generic.rename": "Rename", + "generic.save": "Save", + "generic.skip_alt": "&Skip", + "generic.skip": "Skip", "help.visit_github": "Visit GitHub Repository", "home.search_entries": "Search Entries", "home.search_library": "Search Library", @@ -93,14 +101,41 @@ "home.thumbnail_size.extra_large": "Extra Large Thumbnails", "home.thumbnail_size.large": "Large Thumbnails", "home.thumbnail_size.medium": "Medium Thumbnails", - "home.thumbnail_size.small": "Small Thumbnails", "home.thumbnail_size.mini": "Mini Thumbnails", + "home.thumbnail_size.small": "Small Thumbnails", "home.thumbnail_size": "Thumbnail Size", "ignore_list.add_extension": "&Add Extension", "ignore_list.mode.exclude": "Exclude", "ignore_list.mode.include": "Include", "ignore_list.mode.label": "List Mode:", "ignore_list.title": "File Extensions", + "json_migration.checking_for_parity": "Checking for Parity...", + "json_migration.creating_database_tables": "Creating SQL Database Tables...", + "json_migration.description": "
Start and preview the results of the library migration process. The converted library will not be used unless you click \"Finish Migration\".

Library data should either have matching values or feature a \"Matched\" label. Values that do not match will be displayed in red and feature a \"(!)\" symbol next to them.
This process may take up to several minutes for larger libraries.
", + "json_migration.discrepancies_found.description": "Discrepancies were found between the original and converted library formats. Please review and choose to whether continue with the migration or to cancel.", + "json_migration.discrepancies_found": "Library Discrepancies Found", + "json_migration.finish_migration": "Finish Migration", + "json_migration.heading.aliases": "Aliases:", + "json_migration.heading.colors": "Colors:", + "json_migration.heading.differ": "Discrepancy", + "json_migration.heading.entires": "Entries:", + "json_migration.heading.extension_list_type": "Extension List Type:", + "json_migration.heading.fields": "Fields:", + "json_migration.heading.file_extension_list": "File Extension List:", + "json_migration.heading.match": "Matched", + "json_migration.heading.parent_tags": "Parent Tags:", + "json_migration.heading.paths": "Paths:", + "json_migration.heading.shorthands": "Shorthands:", + "json_migration.heading.tags": "Tags:", + "json_migration.info.description": "Library save files created with TagStudio versions 9.4 and below will need to be migrated to the new v9.5+ format.

What you need to know:

  • Your existing library save file will NOT be deleted
  • Your personal files will NOT be deleted, moved, or modified
  • The new v9.5+ save format can not be opened in earlier versions of TagStudio
", + "json_migration.migrating_files_entries": "Migrating {entries:,d} File Entries...", + "json_migration.migration_complete_with_discrepancies": "Migration Complete, Discrepancies Found", + "json_migration.migration_complete": "Migration Complete!", + "json_migration.start_and_preview": "Start and Preview", + "json_migration.title.new_lib": "

v9.5+ Library

", + "json_migration.title.old_lib": "

v9.4 Library

", + "json_migration.title": "Save Format Migration: \"{path}\"", + "landing.open_create_library": "Open/Create Library {shortcut}", "library.field.add": "Add Field", "library.field.confirm_remove": "Are you sure you want to remove this \"{name}\" field?", "library.field.mixed_data": "Mixed Data", @@ -114,6 +149,7 @@ "library.scan_library.title": "Scanning Library", "macros.running.dialog.new_entries": "Running Configured Macros on {count}/{total} New Entries", "macros.running.dialog.title": "Running Macros on New Entries", + "media_player.autoplay": "Autoplay", "menu.edit.ignore_list": "Ignore Files and Folders", "menu.edit.manage_file_extensions": "Manage File Extensions", "menu.edit.manage_tags": "Manage Tags", @@ -123,9 +159,9 @@ "menu.file.new_library": "New Library", "menu.file.open_create_library": "&Open/Create Library", "menu.file.open_library": "Open Library", - "menu.file.save_library": "Save Library", - "menu.file.save_backup": "&Save Library Backup", "menu.file.refresh_directories": "&Refresh Directories", + "menu.file.save_backup": "&Save Library Backup", + "menu.file.save_library": "Save Library", "menu.file": "&File", "menu.help": "&Help", "menu.macros.folders_to_tags": "Folders to Tags", @@ -143,74 +179,38 @@ "settings.show_filenames_in_grid": "Show Filenames in Grid", "settings.show_recent_libraries": "Show Recent Libraries", "splash.opening_library": "Opening Library \"{library_path}\"...", - "status.library_closing": "Closing Library...", - "status.library_closed": "Library Closed ({time_span})", "status.library_backup_in_progress": "Saving Library Backup...", "status.library_backup_success": "Library Backup Saved at: \"{path}\" ({time_span})", + "status.library_closed": "Library Closed ({time_span})", + "status.library_closing": "Closing Library...", "status.library_save_success": "Library Saved and Closed!", "status.library_search_query": "Searching Library...", "status.results_found": "{count} Results Found ({time_span})", "status.results": "Results", "tag_manager.title": "Library Tags", "tag.add_to_search": "Add to Search", - "tag.edit": "Edit Tag", - "tag.add": "Add Tag", "tag.add.plural": "Add Tags", - "tag.create": "Create Tag", - "tag.remove": "Remove Tag", + "tag.add": "Add Tag", "tag.aliases": "Aliases", "tag.color": "Color", + "tag.confirm_delete": "Are you sure you want to delete the tag \"{tag_name}\"?", + "tag.create": "Create Tag", + "tag.edit": "Edit Tag", "tag.name": "Name", "tag.new": "New Tag", "tag.parent_tags.add": "Add Parent Tag(s)", "tag.parent_tags.description": "This tag can be treated as a substitute for any of these Parent Tags in searches.", "tag.parent_tags": "Parent Tags", + "tag.remove": "Remove Tag", "tag.search_for_tag": "Search for Tag", "tag.shorthand": "Shorthand", "tag.tag_name_required": "Tag Name (Required)", - "tag.confirm_delete": "Are you sure you want to delete the tag \"{tag_name}\"?", "view.size.0": "Mini", "view.size.1": "Small", "view.size.2": "Medium", "view.size.3": "Large", "view.size.4": "Extra Large", - "window.title.error": "Error", - "window.title.open_create_library": "Open/Create Library", "window.message.error_opening_library": "Error opening library.", - "drop_import.title": "Conflicting File(s)", - "drop_import.decription": "The following files have filenames already exist in the library", - "drop_import.progress.label.initial": "Importing New Files...", - "drop_import.progress.label.singular": "Importing New Files...\n1 File imported.{suffix}", - "drop_import.progress.label.plural": "Importing New Files...\n{count} Files Imported.{suffix}", - "drop_import.progress.window_title": "Import Files", - "drop_import.duplicates_choice.singular": "The following file has a filename that already exists in the library.", - "drop_import.duplicates_choice.plural": "The following {count} files have filenames that already exist in the library.", - "landing.open_create_library": "Open/Create Library {shortcut}", - "media_player.autoplay": "Autoplay", - "json_migration.title": "Save Format Migration: \"{path}\"", - "json_migration.info.description": "Library save files created with TagStudio versions 9.4 and below will need to be migrated to the new v9.5+ format.

What you need to know:

  • Your existing library save file will NOT be deleted
  • Your personal files will NOT be deleted, moved, or modified
  • The new v9.5+ save format can not be opened in earlier versions of TagStudio
", - "json_migration.title.old_lib": "

v9.4 Library

", - "json_migration.title.new_lib": "

v9.5+ Library

", - "json_migration.start_and_preview": "Start and Preview", - "json_migration.finish_migration": "Finish Migration", - "json_migration.checking_for_parity": "Checking for Parity...", - "json_migration.creating_database_tables": "Creating SQL Database Tables...", - "json_migration.migrating_files_entries": "Migrating {entries:,d} File Entries...", - "json_migration.migration_complete": "Migration Complete!", - "json_migration.migration_complete_with_discrepancies": "Migration Complete, Discrepancies Found", - "json_migration.discrepancies_found": "Library Discrepancies Found", - "json_migration.discrepancies_found.description": "Discrepancies were found between the original and converted library formats. Please review and choose to whether continue with the migration or to cancel.", - "json_migration.heading.match": "Matched", - "json_migration.heading.differ": "Discrepancy", - "json_migration.heading.entires": "Entries:", - "json_migration.heading.tags": "Tags:", - "json_migration.heading.shorthands": "Shorthands:", - "json_migration.heading.parent_tags": "Parent Tags:", - "json_migration.heading.aliases": "Aliases:", - "json_migration.heading.colors": "Colors:", - "json_migration.heading.file_extension_list": "File Extension List:", - "json_migration.heading.extension_list_type": "Extension List Type:", - "json_migration.heading.paths": "Paths:", - "json_migration.heading.fields": "Fields:", - "json_migration.description": "
Start and preview the results of the library migration process. The converted library will not be used unless you click \"Finish Migration\".

Library data should either have matching values or feature a \"Matched\" label. Values that do not match will be displayed in red and feature a \"(!)\" symbol next to them.
This process may take up to several minutes for larger libraries.
" + "window.title.error": "Error", + "window.title.open_create_library": "Open/Create Library" } diff --git a/tagstudio/src/qt/modals/drop_import.py b/tagstudio/src/qt/modals/drop_import.py index 92b9f1672..9b13b3d11 100644 --- a/tagstudio/src/qt/modals/drop_import.py +++ b/tagstudio/src/qt/modals/drop_import.py @@ -51,7 +51,7 @@ def __init__(self, driver: "QtDriver"): self.desc_widget = QLabel() self.desc_widget.setObjectName("descriptionLabel") self.desc_widget.setWordWrap(True) - self.desc_widget.setText(Translations["drop_import.decription"]) + self.desc_widget.setText(Translations["drop_import.description"]) self.desc_widget.setAlignment(Qt.AlignmentFlag.AlignCenter) # Duplicate File List ========