From 94f0397e04b10f3c30293c05ca6e6a810037905a Mon Sep 17 00:00:00 2001 From: Tyrannicodin Date: Wed, 4 Sep 2024 22:04:50 +0100 Subject: [PATCH 1/9] Translator implemenetation --- tagstudio/src/qt/translator.py | 38 ++++++++++++++++++++++++++++++++++ tagstudio/src/qt/ts_qt.py | 12 +++++++++++ 2 files changed, 50 insertions(+) create mode 100644 tagstudio/src/qt/translator.py diff --git a/tagstudio/src/qt/translator.py b/tagstudio/src/qt/translator.py new file mode 100644 index 000000000..0bbb0f85b --- /dev/null +++ b/tagstudio/src/qt/translator.py @@ -0,0 +1,38 @@ +# Copyright (C) 2024 Travis Abendshien (CyanVoxel). +# Licensed under the GPL-3.0 License. +# Created for TagStudio: https://github.com/CyanVoxel/TagStudio + +from PySide6.QtCore import QObject, QTranslator +from pathlib import Path +from json import loads + + +class TSTranslator(QTranslator): + def __init__(self, parent: QObject | None = None) -> None: + super().__init__(parent) + self.translations: dict[str, str] = {} + + def translate( + self, context, sourceText, disambiguation: str | None = None, n: int = -1 + ) -> str: + return self.translations.get(context + "." + sourceText.replace(" ", "")) + + def load(self, translationDir: Path, language: str, country: str = "") -> bool: + file = None + if (translationDir / (language + "_" + country + ".ini")).exists(): + file = open( + translationDir / (language + "_" + country + ".ini"), encoding="utf-8" + ) + elif (translationDir / (language + ".ini")).exists(): + file = open(translationDir / (language + ".ini"), encoding="utf-8") + if file is None: + return False + + for line in file.readlines(): + if line.startswith("#") or line == "\n": + continue + identifier, translation = line.split("=", 1) + self.translations[identifier] = loads(translation) + + file.close() + return True diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index be7403683..febbb89ff 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -29,6 +29,7 @@ QThreadPool, QTimer, QSettings, + QLocale, ) from PySide6.QtGui import ( QGuiApplication, @@ -85,6 +86,7 @@ from src.qt.modals.fix_unlinked import FixUnlinkedEntriesModal from src.qt.modals.fix_dupes import FixDupeFilesModal from src.qt.modals.folders_to_tags import FoldersToTagsModal +from src.qt.translator import TSTranslator # this import has side-effect of import PySide resources import src.qt.resources_rc # pylint: disable=unused-import @@ -244,6 +246,16 @@ def start(self) -> None: if os.name == "nt": sys.argv += ["-platform", "windows:darkmode=2"] app = QApplication(sys.argv) + + translator = TSTranslator() + path = Path(__file__).parents[2] / "resources/translations" + translator.load( + path, + QLocale.languageToCode(QLocale.system().language()).lower(), + QLocale.countryToCode(QLocale.system().country()).lower(), + ) + app.installTranslator(translator) + app.setStyle("Fusion") # pal: QPalette = app.palette() # pal.setColor(QPalette.ColorGroup.Active, From c6806b85446edde5d68dfda97c3b8267c0804d41 Mon Sep 17 00:00:00 2001 From: Tyrannicodin Date: Wed, 4 Sep 2024 22:05:08 +0100 Subject: [PATCH 2/9] Very limited initial translations --- tagstudio/resources/translations/en_us.ini | 10 ++++++++++ tagstudio/src/qt/main_window.py | 12 ++++++------ tagstudio/src/qt/ts_qt.py | 7 +++++-- tagstudio/src/qt/ui/home_ui.py | 12 ++++++------ tagstudio/src/qt/widgets/preview_panel.py | 4 ++-- 5 files changed, 29 insertions(+), 16 deletions(-) create mode 100644 tagstudio/resources/translations/en_us.ini diff --git a/tagstudio/resources/translations/en_us.ini b/tagstudio/resources/translations/en_us.ini new file mode 100644 index 000000000..90e2c560a --- /dev/null +++ b/tagstudio/resources/translations/en_us.ini @@ -0,0 +1,10 @@ +MainWindow.Title="Main Window" +MainWindow.ThumbnailSize="Thumbnail Size" +MainWindow.RecentLibraries="Recent Libraries" +MainWindow.Search.Search="Search" +MainWindow.Search.Entries="Search Entries" +MainWindow.Search.AND="And (Includes All Tags)" +MainWindow.Search.OR="Or (Includes Any Tag)" + +MenuBar.File.Title="&File" +MenuBar.File.OpenCreateLibrary="&Open/Create Library" \ No newline at end of file diff --git a/tagstudio/src/qt/main_window.py b/tagstudio/src/qt/main_window.py index a77f87447..3a582b134 100644 --- a/tagstudio/src/qt/main_window.py +++ b/tagstudio/src/qt/main_window.py @@ -196,7 +196,7 @@ def setupUi(self, MainWindow): def retranslateUi(self, MainWindow): MainWindow.setWindowTitle(QCoreApplication.translate( - "MainWindow", u"MainWindow", None)) + "MainWindow", u"Title", None)) # Navigation buttons self.backButton.setText( QCoreApplication.translate("MainWindow", u"<", None)) @@ -205,18 +205,18 @@ def retranslateUi(self, MainWindow): # Search field self.searchField.setPlaceholderText( - QCoreApplication.translate("MainWindow", u"Search Entries", None)) + QCoreApplication.translate("MainWindow.Search", u"Entries", None)) self.searchButton.setText( - QCoreApplication.translate("MainWindow", u"Search", None)) + QCoreApplication.translate("MainWindow.Search", u"Search", None)) # Search type selector - self.comboBox_2.setItemText(0, QCoreApplication.translate("MainWindow", "And (Includes All Tags)")) - self.comboBox_2.setItemText(1, QCoreApplication.translate("MainWindow", "Or (Includes Any Tag)")) + self.comboBox_2.setItemText(0, QCoreApplication.translate("MainWindow.Search", u"AND")) + self.comboBox_2.setItemText(1, QCoreApplication.translate("MainWindow.Search", u"OR")) self.comboBox.setCurrentText("") # Thumbnail size selector self.comboBox.setPlaceholderText( - QCoreApplication.translate("MainWindow", u"Thumbnail Size", None)) + QCoreApplication.translate("MainWindow", u"ThumbnailSize", None)) # retranslateUi def moveEvent(self, event) -> None: diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index febbb89ff..9ffb22de0 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -30,6 +30,7 @@ QTimer, QSettings, QLocale, + QCoreApplication, ) from PySide6.QtGui import ( QGuiApplication, @@ -309,7 +310,7 @@ def start(self) -> None: self.main_window.setMenuBar(menu_bar) menu_bar.setNativeMenuBar(True) - file_menu = QMenu("&File", menu_bar) + file_menu = QMenu(QCoreApplication.translate("MenuBar.File", "Title"), menu_bar) edit_menu = QMenu("&Edit", menu_bar) tools_menu = QMenu("&Tools", menu_bar) macros_menu = QMenu("&Macros", menu_bar) @@ -320,7 +321,9 @@ def start(self) -> None: # file_menu.addAction(QAction('&New Library', menu_bar)) # file_menu.addAction(QAction('&Open Library', menu_bar)) - open_library_action = QAction("&Open/Create Library", menu_bar) + open_library_action = QAction( + QCoreApplication.translate("MenuBar.File", "OpenCreateLibrary"), menu_bar + ) open_library_action.triggered.connect(lambda: self.open_library_from_dialog()) open_library_action.setShortcut( QtCore.QKeyCombination( diff --git a/tagstudio/src/qt/ui/home_ui.py b/tagstudio/src/qt/ui/home_ui.py index 0d986f6ad..d66d832de 100644 --- a/tagstudio/src/qt/ui/home_ui.py +++ b/tagstudio/src/qt/ui/home_ui.py @@ -146,15 +146,15 @@ def setupUi(self, MainWindow): # setupUi 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)) + MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"Title", None)) + self.comboBox_2.setItemText(0, QCoreApplication.translate("MainWindow.Search", u"AND")) + self.comboBox_2.setItemText(1, QCoreApplication.translate("MainWindow.Search", u"OR")) self.comboBox.setCurrentText("") - self.comboBox.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Thumbnail Size", None)) + self.comboBox.setPlaceholderText(QCoreApplication.translate("MainWindow", u"ThumbnailSize", 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)) - self.searchButton.setText(QCoreApplication.translate("MainWindow", u"Search", None)) + self.searchField.setPlaceholderText(QCoreApplication.translate("MainWindow.Search", u"SearchEntries", None)) + self.searchButton.setText(QCoreApplication.translate("MainWindow.Search", u"Search", None)) # retranslateUi diff --git a/tagstudio/src/qt/widgets/preview_panel.py b/tagstudio/src/qt/widgets/preview_panel.py index 27320aabf..e374cbf78 100644 --- a/tagstudio/src/qt/widgets/preview_panel.py +++ b/tagstudio/src/qt/widgets/preview_panel.py @@ -12,7 +12,7 @@ import rawpy from PIL import Image, UnidentifiedImageError from PIL.Image import DecompressionBombError -from PySide6.QtCore import Signal, Qt, QSize +from PySide6.QtCore import Signal, Qt, QSize, QCoreApplication from PySide6.QtGui import QResizeEvent, QAction from PySide6.QtWidgets import ( QWidget, @@ -274,7 +274,7 @@ def clear_layout(layout_item: QVBoxLayout): # remove any potential previous items clear_layout(layout) - label = QLabel("Recent Libraries") + label = QLabel(QCoreApplication.translate("MainWindow", "RecentLibraries")) label.setAlignment(Qt.AlignCenter) # type: ignore row_layout = QHBoxLayout() From 225a8ef872b8a22265a0a7241141c8b03308e178 Mon Sep 17 00:00:00 2001 From: Tyrannicodin Date: Wed, 25 Sep 2024 21:36:29 +0100 Subject: [PATCH 3/9] Translate ts_qt.py --- tagstudio/src/qt/ts_qt.py | 97 +++++++++++++++++++++++++++------------ 1 file changed, 67 insertions(+), 30 deletions(-) diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index 8f3653ac4..25741b487 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -149,7 +149,9 @@ def __init__(self, backend, args): self.spacing = None self.branch: str = (" (" + VERSION_BRANCH + ")") if VERSION_BRANCH else "" - self.base_title: str = f"TagStudio Alpha {VERSION}{self.branch}" + self.base_title: str = QCoreApplication.translate("home", "base_title").format( + version=VERSION, branch=self.branch + ) # self.title_text: str = self.base_title # self.buffer = {} self.thumb_job_queue: Queue = Queue() @@ -278,19 +280,19 @@ def start(self) -> None: self.main_window.setMenuBar(menu_bar) menu_bar.setNativeMenuBar(True) - file_menu = QMenu(QCoreApplication.translate("MenuBar.File", "Title"), menu_bar) - edit_menu = QMenu("&Edit", menu_bar) - tools_menu = QMenu("&Tools", menu_bar) - macros_menu = QMenu("&Macros", menu_bar) - window_menu = QMenu("&Window", menu_bar) - help_menu = QMenu("&Help", menu_bar) + file_menu = QMenu(QCoreApplication.translate("menu", "file"), menu_bar) + edit_menu = QMenu(QCoreApplication.translate("menu", "edit"), menu_bar) + tools_menu = QMenu(QCoreApplication.translate("menu", "tools"), menu_bar) + macros_menu = QMenu(QCoreApplication.translate("menu", "macros"), menu_bar) + window_menu = QMenu(QCoreApplication.translate("menu", "window"), menu_bar) + help_menu = QMenu(QCoreApplication.translate("menu", "help"), menu_bar) # File Menu ============================================================ # file_menu.addAction(QAction('&New Library', menu_bar)) # file_menu.addAction(QAction('&Open Library', menu_bar)) open_library_action = QAction( - QCoreApplication.translate("MenuBar.File", "OpenCreateLibrary"), menu_bar + QCoreApplication.translate("dialog", "open_create_library"), menu_bar ) open_library_action.triggered.connect(lambda: self.open_library_from_dialog()) open_library_action.setShortcut( @@ -342,7 +344,7 @@ def start(self) -> None: file_menu.addAction(close_library_action) # Edit Menu ============================================================ - new_tag_action = QAction("New &Tag", menu_bar) + new_tag_action = QAction(QCoreApplication.translate("tag", "new"), menu_bar) new_tag_action.triggered.connect(lambda: self.add_tag_action_callback()) new_tag_action.setShortcut( QtCore.QKeyCombination( @@ -400,7 +402,9 @@ 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( + QCoreApplication.translate("fix_unlinked", "fix_unlinked"), menu_bar + ) fix_unlinked_entries_action.triggered.connect(create_fix_unlinked_entries_modal) tools_menu.addAction(fix_unlinked_entries_action) @@ -409,7 +413,9 @@ 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( + QCoreApplication.translate("fix_dupes", "fix_dupes"), menu_bar + ) fix_dupe_files_action.triggered.connect(create_dupe_files_modal) tools_menu.addAction(fix_dupe_files_action) @@ -495,7 +501,7 @@ def create_folders_tags_modal(): if lib: self.splash.showMessage( - f'Opening Library "{lib}"...', + QCoreApplication.translate("splash", "open_library").format(lib=lib), int(Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignHCenter), QColor("#9782ff"), ) @@ -586,7 +592,9 @@ 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( + QCoreApplication.translate("logging", "closing") + ) start_time = time.time() self.settings.setValue(SettingItems.LAST_LIBRARY, self.lib.library_dir) @@ -616,7 +624,9 @@ def close_library(self, is_shutdown: bool = False): def backup_library(self): logger.info("Backing Up Library...") - self.main_window.statusbar.showMessage("Saving Library...") + self.main_window.statusbar.showMessage( + QCoreApplication.translate("logging", "saving") + ) start_time = time.time() target_path = self.lib.save_library_backup_to_disk() end_time = time.time() @@ -627,8 +637,8 @@ def backup_library(self): def add_tag_action_callback(self): self.modal = PanelModal( BuildTagPanel(self.lib), - "New Tag", - "Add Tag", + QCoreApplication.translate("tag", "new"), + QCoreApplication.translate("tag", "add"), has_save=True, ) @@ -660,7 +670,10 @@ def clear_select_action_callback(self): def show_tag_database(self): self.modal = PanelModal( - TagDatabasePanel(self.lib), "Library Tags", "Library Tags", has_save=False + TagDatabasePanel(self.lib), + QCoreApplication.translate("tag", "library"), + QCoreApplication.translate("tag", "library"), + has_save=False, ) self.modal.show() @@ -668,8 +681,8 @@ def show_file_extension_modal(self): panel = FileExtensionModal(self.lib) self.modal = PanelModal( panel, - "File Extensions", - "File Extensions", + QCoreApplication.translate("generic", "file_extension"), + QCoreApplication.translate("generic", "file_extension"), has_save=True, ) @@ -682,8 +695,8 @@ def add_new_files_callback(self): tracker = RefreshDirTracker(self.lib) pw = ProgressWidget( - window_title="Refreshing Directories", - label_text="Scanning Directories for New Files...\nPreparing...", + window_title=QCoreApplication.translate("dialog", "refresh_directories"), + label_text=QCoreApplication.translate("dialog", "scan_directories"), cancel_button_text=None, minimum=0, maximum=0, @@ -695,7 +708,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} File{"s" if x + 1 != 1 else ""} Searched, {tracker.files_count} New Files Found' + QCoreApplication.translate( + "dialog", "scan_directories.new_files" + ).format( + file_count=x + 1, + plural="s" if x + 1 != 1 else "", + new_files=tracker.files_count, + ) ), ) ) @@ -735,8 +754,12 @@ def add_new_files_runnable(self, tracker: RefreshDirTracker): # iterator = FunctionIterator(lambda: self.new_file_macros_runnable(tracker.files_not_in_library)) 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", + window_title=QCoreApplication.translate( + "progression.running_macros", "running" + ), + label_text=QCoreApplication.translate( + "progression.running_macros", "new_entries" + ).format(done_files=1, new_files=files_count), cancel_button_text=None, minimum=0, maximum=files_count, @@ -746,7 +769,9 @@ def add_new_files_runnable(self, tracker: RefreshDirTracker): lambda x: ( pw.update_progress(x + 1), pw.update_label( - f"Running Configured Macros on {x + 1}/{files_count} New Entries" + QCoreApplication.translate( + "progression.running_macros", "new_entries" + ).format(done_files=x + 1, new_files=files_count) ), ) ) @@ -1019,7 +1044,9 @@ def filter_items(self, filter: FilterState | None = None) -> None: self.filter = dataclasses.replace(self.filter, **dataclasses.asdict(filter)) self.main_window.statusbar.showMessage( - f'Searching Library: "{self.filter.summary}"' + QCoreApplication.translate("status", "search_library_query").format( + query=self.filter.summary + ) ) self.main_window.statusbar.repaint() start_time = time.time() @@ -1031,11 +1058,17 @@ def filter_items(self, filter: FilterState | None = None) -> None: end_time = time.time() if self.filter.summary: self.main_window.statusbar.showMessage( - f'{query_count} Results Found for "{self.filter.summary}" ({format_timespan(end_time - start_time)})' + QCoreApplication.translate("status", "queried_results_found").format( + results=query_count, + query=self.filter.summary, + time=format_timespan(end_time - start_time), + ), ) else: self.main_window.statusbar.showMessage( - f"{query_count} Results ({format_timespan(end_time - start_time)})" + QCoreApplication.translate("status", "results_found").format( + results=query_count, time=format_timespan(end_time - start_time) + ) ) # update page content @@ -1090,7 +1123,9 @@ def update_libs_list(self, path: Path | str): def open_library(self, path: Path | str): """Opens a TagStudio library.""" - open_message: str = f'Opening Library "{str(path)}"...' + open_message: str = QCoreApplication.translate("splash", "open_library").format( + lib=str(path) + ) self.main_window.landing_widget.set_status_label(open_message) self.main_window.statusbar.showMessage(open_message, 3) self.main_window.repaint() @@ -1103,7 +1138,9 @@ def open_library(self, path: Path | str): self.add_new_files_callback() self.update_libs_list(path) - title_text = f"{self.base_title} - Library '{self.lib.library_dir}'" + title_text = QCoreApplication.translate("open_library", "title").format( + base_title=self.base_title, directory=self.lib.library_dir + ) self.main_window.setWindowTitle(title_text) self.selected.clear() From 013e331b3a9e82ad0e140359779ca5860398988f Mon Sep 17 00:00:00 2001 From: Tyrannicodin Date: Wed, 25 Sep 2024 21:36:44 +0100 Subject: [PATCH 4/9] Convert translator to use json files --- tagstudio/src/qt/translator.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/tagstudio/src/qt/translator.py b/tagstudio/src/qt/translator.py index 0bbb0f85b..8026c9a17 100644 --- a/tagstudio/src/qt/translator.py +++ b/tagstudio/src/qt/translator.py @@ -4,7 +4,7 @@ from PySide6.QtCore import QObject, QTranslator from pathlib import Path -from json import loads +from json import load as load_json class TSTranslator(QTranslator): @@ -19,20 +19,16 @@ def translate( def load(self, translationDir: Path, language: str, country: str = "") -> bool: file = None - if (translationDir / (language + "_" + country + ".ini")).exists(): - file = open( - translationDir / (language + "_" + country + ".ini"), encoding="utf-8" + if (translationDir / (language + "_" + country + ".json")).exists(): + file = load_json( # noqa: SIM115 + translationDir / (language + "_" + country + ".json"), encoding="utf-8" ) - elif (translationDir / (language + ".ini")).exists(): - file = open(translationDir / (language + ".ini"), encoding="utf-8") + elif (translationDir / (language + ".json")).exists(): + file = load_json(translationDir / (language + ".json"), encoding="utf-8") # noqa: SIM115 if file is None: return False - for line in file.readlines(): - if line.startswith("#") or line == "\n": - continue - identifier, translation = line.split("=", 1) - self.translations[identifier] = loads(translation) + self.translations = file file.close() return True From deea2d9262bd6a57c6bc599bfda1eb28290d05b7 Mon Sep 17 00:00:00 2001 From: Coolio Date: Sat, 23 Nov 2024 15:26:08 -0600 Subject: [PATCH 5/9] Fixed json file reading --- tagstudio/resources/translations/en_us.ini | 10 -- tagstudio/resources/translations/en_us.json | 144 ++++++++++++++++++++ tagstudio/src/qt/translator.py | 43 +++--- 3 files changed, 166 insertions(+), 31 deletions(-) delete mode 100644 tagstudio/resources/translations/en_us.ini create mode 100644 tagstudio/resources/translations/en_us.json diff --git a/tagstudio/resources/translations/en_us.ini b/tagstudio/resources/translations/en_us.ini deleted file mode 100644 index 90e2c560a..000000000 --- a/tagstudio/resources/translations/en_us.ini +++ /dev/null @@ -1,10 +0,0 @@ -MainWindow.Title="Main Window" -MainWindow.ThumbnailSize="Thumbnail Size" -MainWindow.RecentLibraries="Recent Libraries" -MainWindow.Search.Search="Search" -MainWindow.Search.Entries="Search Entries" -MainWindow.Search.AND="And (Includes All Tags)" -MainWindow.Search.OR="Or (Includes Any Tag)" - -MenuBar.File.Title="&File" -MenuBar.File.OpenCreateLibrary="&Open/Create Library" \ No newline at end of file diff --git a/tagstudio/resources/translations/en_us.json b/tagstudio/resources/translations/en_us.json new file mode 100644 index 000000000..6f37c3788 --- /dev/null +++ b/tagstudio/resources/translations/en_us.json @@ -0,0 +1,144 @@ +{ + "home.base_title": "TagStudio Alpha", + "home.main_window": "Main Window", + "home.include_all_tags": "And (Includes All Tags)", + "home.include_any_tag": "Or (Includes Any Tag)", + "home.thumbnail_size": "Thumbnail Size", + "home.search_entries": "Search Entries", + "home.search": "Search", + "menu.file": "File", + "menu.edit": "Edit", + "menu.tools": "Tools", + "menu.macros": "Macros", + "menu.window": "Window", + "menu.help": "Help", + "tag.new": "New Tag", + "tag.add": "Add Tag", + "tag.library": "Library Tags", + "merge.window_title": "Merging Duplicate Entries", + "merge.merge_dupe_entries": "Merging Duplicate Entries", + "preview.dimensions": "Dimensions", + "preview.recent": "Recent Libraries", + "library.title": "Title", + "library.author": "Author", + "library.Artist": "Artist", + "library.url": "URL", + "library.description": "Description", + "library.notes": "Notes", + "library.tags": "Tags", + "library.content_tags": "Content Tags", + "library.meta_tags": "Meta Tags", + "library.collation": "Collation", + "library.date": "Date", + "library.date_created": "Date Created", + "library.date_modified": "Date Modified", + "library.date_taken": "Date Taken", + "library.date_published": "Date Published", + "library.archived": "Date Archived", + "library.favorite": "Favorite", + "library.book": "Book", + "library.comic": "Comic", + "library.series": "Series", + "library.manga": "Manga", + "library.source": "Source", + "library.date_uploaded": "Date Uploaded", + "library.date_released": "Date Released", + "library.volume": "Volume", + "library.anthology": "Anthology", + "library.magazine": "Magazine", + "library.publisher": "Publisher", + "library.guest_artist": "Guest Artist", + "library.composer": "Composer", + "library.comments": "Comments", + "open_library.no_tagstudio_library_found": "No existing TagStudio library found at '%{path}'. Creating one.", + "open_library.library_creation_return_code": "Library Creation Return Code:", + "open_library.title": "Library", + "dialog.open_create_library": "Open/Create Library", + "splash.open_library": "Opening Library", + "status.save_success": "Library Saved and Closed!", + "status.backup_success": "Library Backup Saved at:", + "status.search_library_query": "Searching Library for", + "status.enumerate_query": "Query:%{query}, Frame: %{i}, Length: %{len(f)}", + "status.number_results_found": "%{len(all_items)} Results Found for \"%{query}\" (%{format_timespan(end_time - start_time)})", + "status.results_found": "Results", + "dialog.save_library": "Save Library", + "dialog.refresh_directories": "Refreshing Directories", + "dialog.scan_directories": "Scanning Directories for New Files...\nPreparing...", + "dialog.scan_directories.new_files": "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", + "tooltip.open_library": "Ctrl+O", + "tooltip.save_library": "Ctrl+S", + "progression.running_macros.new_entries": "Running Macros on New Entries", + "progression.running_macros.one_new_entry": "Running Configured Macros on 1/%{len(new_ids)} New Entries", + "progression.running_macros.several_new_entry": "Running Configured Macros on %{x + 1}/%{len(new_ids)} New Entries", + "file_opener.open_file": "Opening file:}", + "file_opener.not_found": "File not found:", + "file_opener.command_not_found": "Could not find %{command_name} on system PATH", + "add_field.add": "Add Field", + "generic.remove_field": "Remove Field", + "generic.file_extension": "File Extensions", + "generic.open_file": "Open file", + "generic.open_file_explorer": "Open file in explorer", + "generic.cancel": "Cancel", + "generic.add": "Add", + "generic.name": "Name", + "generic.shorthand": "Shorthand", + "generic.aliases": "Aliases", + "generic.color": "Color", + "generic.delete": "Delete", + "generic.exclude": "Exclude", + "generic.include": "Include", + "generic.done": "Done", + "generic.open_all": "Open All", + "generic.close_all": "Close All", + "generic.refresh_all": "Refresh All", + "generic.apply": "Apply", + "generic.mirror": "Mirror", + "generic.search_tags": "Search Tags", + "build_tags.parent_tags": "Parent Tags", + "build_tags.add_parent_tags": "Add Parent Tags", + "delete_unlinked.delete_unlinked": "Delete Unlinked Entries", + "delete_unlinked.confirm": "Are you sure you want to delete the following %{len(self.lib.missing_files)} entries?", + "delete_unlinked.delete_entries": "Deleting Entries", + "delete_unlinked.deleting_number_entries": "Deleting %{x[0]+1}/{len(self.lib.missing_files)} Unlinked Entries", + "file_extension.add_extension": "Add Extension", + "file_extension.list_mode": "List Mode:", + "fix_dupes.fix_dupes": "Fix Duplicate Files", + "fix_dupes.no_file_selected": "No DupeGuru File Selected", + "fix_dupes.load_file": "Load DupeGuru File", + "fix_dupes.mirror_entries": "Mirror Entries", + "fix_dupes.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.", + "fix_dupes.advice_label": "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.", + "fix_dupes.open_result_files": "Open DupeGuru Results File", + "fix_dupes.name_filter": "DupeGuru Files (*.dupeguru)", + "fix_dupes.no_file_match": "Duplicate File Matches: N/A", + "fix_dupes.number_file_match": "Duplicate File Matches: %{count}", + "fix_unlinked.fix_unlinked": "Fix Unlinked Entries", + "fix_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, manually relinked by the user, or deleted if desired.", + "fix_unlinked.duplicate_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.", + "fix_unlinked.search_and_relink": "Search && Relink", + "fix_unlinked.refresh_dupes": "Refresh Duplicate Entries", + "fix_unlinked.merge_dupes": "Merge Duplicate Entries", + "fix_unlinked.manual_relink": "Manual Relink", + "fix_unlinked.delete_unlinked": "Delete Unlinked Entries", + "fix_unlinked.scan_library.title": "Scanning Library", + "fix_unlinked.scan_library.label": "Scanning Library for Unlinked Entries...", + "folders_to_tags.folders_to_tags": "Converting folders to Tags", + "folders_to_tags.title": "Create Tags From Folders", + "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.", + "mirror_entities.are_you_sure": "Are you sure you want to mirror the following %{len(self.lib.dupe_files)} Entries?", + "mirror_entities.title": "Mirroring Entries", + "mirror_entities.label": "Mirroring 1/%{count} Entries...", + "relink_unlinked.title": "Relinking Entries", + "relink_unlinked.attempt_relink": "Attempting to Relink %{x[0]+1}/%{len(self.lib.missing_files)} Entries, %{self.fixed} Successfully Relinked", + "landing.open_button": "Open/Create Library %{open_shortcut_text}", + "preview_panel.missing_location": "Location is missing", + "preview_panel.update_widgets": "[ENTRY PANEL] UPDATE WIDGETS (%{self.driver.selected})", + "preview_panel.no_items_selected": "No Items Selected", + "preview_panel.confirm_remove": "Are you sure you want to remove this \"%{self.lib.get_field_attr(field, \"name\")}\" field?", + "preview_panel.mixed_data": "Mixed Data", + "preview_panel.edit_name": "Edit", + "preview_panel.unknown_field_type": "Unknown Field Type", + "tag.search_for_tag": "Search for Tag", + "tag.add_search": "Add to Search", + "text_line_edit.unknown_event_type": "unknown event type: %{event}" +} \ No newline at end of file diff --git a/tagstudio/src/qt/translator.py b/tagstudio/src/qt/translator.py index 8026c9a17..9997bfffd 100644 --- a/tagstudio/src/qt/translator.py +++ b/tagstudio/src/qt/translator.py @@ -8,27 +8,28 @@ class TSTranslator(QTranslator): - def __init__(self, parent: QObject | None = None) -> None: - super().__init__(parent) - self.translations: dict[str, str] = {} + def __init__(self, parent: QObject | None = None) -> None: + super().__init__(parent) + self.translations: dict[str, str] = {} - def translate( - self, context, sourceText, disambiguation: str | None = None, n: int = -1 - ) -> str: - return self.translations.get(context + "." + sourceText.replace(" ", "")) + def translate( + self, context, sourceText, disambiguation: str | None = None, n: int = -1 + ) -> str: + return self.translations.get(context + "." + sourceText.replace(" ", "")) - def load(self, translationDir: Path, language: str, country: str = "") -> bool: - file = None - if (translationDir / (language + "_" + country + ".json")).exists(): - file = load_json( # noqa: SIM115 - translationDir / (language + "_" + country + ".json"), encoding="utf-8" - ) - elif (translationDir / (language + ".json")).exists(): - file = load_json(translationDir / (language + ".json"), encoding="utf-8") # noqa: SIM115 - if file is None: - return False + def load(self, translationDir: Path, language: str, country: str = "") -> bool: + file = None + translations = None + if (translationDir / (language + "_" + country + ".json")).exists(): + file = open(translationDir / (language + "_" + country + ".json"), "r") + translations = load_json(file) + elif (translationDir / (language + ".json")).exists(): + file = open(translationDir / (language + ".json"), "r") + translations = load_json(file) + # noqa: SIM115 + if file is None: + return False - self.translations = file - - file.close() - return True + self.translations = translations + file.close() + return True From ae244e62e66236f7e7e5edef3010e74df7b82bf0 Mon Sep 17 00:00:00 2001 From: Coolio Date: Sat, 23 Nov 2024 15:40:14 -0600 Subject: [PATCH 6/9] Translate build_tag.py --- tagstudio/src/qt/modals/build_tag.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/tagstudio/src/qt/modals/build_tag.py b/tagstudio/src/qt/modals/build_tag.py index 5507fb09a..512eb19ec 100644 --- a/tagstudio/src/qt/modals/build_tag.py +++ b/tagstudio/src/qt/modals/build_tag.py @@ -4,7 +4,7 @@ import structlog -from PySide6.QtCore import Signal, Qt +from PySide6.QtCore import Signal, Qt, QCoreApplication from PySide6.QtWidgets import ( QWidget, QVBoxLayout, @@ -50,7 +50,7 @@ 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") + self.name_title.setText(QCoreApplication.translate("generic", "name")) self.name_layout.addWidget(self.name_title) self.name_field = QLineEdit() self.name_layout.addWidget(self.name_field) @@ -63,7 +63,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(QCoreApplication.translate("generic", "shorthand")) self.shorthand_layout.addWidget(self.shorthand_title) self.shorthand_field = QLineEdit() self.shorthand_layout.addWidget(self.shorthand_field) @@ -76,7 +76,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(QCoreApplication.translate("generic", "aliases")) self.aliases_layout.addWidget(self.aliases_title) self.aliases_field = QTextEdit() self.aliases_field.setAcceptRichText(False) @@ -92,7 +92,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(QCoreApplication.translate("build_tags", "parent_tags")) self.subtags_layout.addWidget(self.subtags_title) self.scroll_contents = QWidget() @@ -114,7 +114,11 @@ def __init__(self, library: Library, tag: Tag | None = None): self.subtags_add_button.setText("+") tsp = TagSearchPanel(self.lib) 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, + QCoreApplication.translate("build_tags", "add_parent_tags"), + QCoreApplication.translate("build_tags", "add_parent_tags"), + ) self.subtags_add_button.clicked.connect(self.add_tag_modal.show) self.subtags_layout.addWidget(self.subtags_add_button) @@ -130,7 +134,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(QCoreApplication.translate("generic", "color")) self.color_layout.addWidget(self.color_title) self.color_field = QComboBox() self.color_field.setEditable(False) @@ -161,7 +165,7 @@ def __init__(self, library: Library, tag: Tag | None = None): # TODO - fill subtags self.subtags: set[int] = set() - self.set_tag(tag or Tag(name="New Tag")) + self.set_tag(tag or Tag(name=QCoreApplication.translate("tag", "new"), )) def add_subtag_callback(self, tag_id: int): logger.info("add_subtag_callback", tag_id=tag_id) From a7d6e30284cda97e165509ba325aae9879aa9346 Mon Sep 17 00:00:00 2001 From: Coolio Date: Sun, 24 Nov 2024 09:36:37 -0600 Subject: [PATCH 7/9] Translate delete_unlinked.py and file_extension.py and fixed ruff error --- tagstudio/src/qt/modals/build_tag.py | 13 ++++++-- tagstudio/src/qt/modals/delete_unlinked.py | 33 ++++++++++-------- tagstudio/src/qt/modals/file_extension.py | 16 +++++---- tagstudio/src/qt/translator.py | 39 +++++++++++----------- 4 files changed, 59 insertions(+), 42 deletions(-) diff --git a/tagstudio/src/qt/modals/build_tag.py b/tagstudio/src/qt/modals/build_tag.py index 512eb19ec..8ab4f1588 100644 --- a/tagstudio/src/qt/modals/build_tag.py +++ b/tagstudio/src/qt/modals/build_tag.py @@ -92,7 +92,9 @@ def __init__(self, library: Library, tag: Tag | None = None): self.subtags_layout.setAlignment(Qt.AlignmentFlag.AlignLeft) self.subtags_title = QLabel() - self.subtags_title.setText(QCoreApplication.translate("build_tags", "parent_tags")) + self.subtags_title.setText( + QCoreApplication.translate("build_tags", "parent_tags") + ) self.subtags_layout.addWidget(self.subtags_title) self.scroll_contents = QWidget() @@ -115,7 +117,7 @@ def __init__(self, library: Library, tag: Tag | None = None): tsp = TagSearchPanel(self.lib) tsp.tag_chosen.connect(lambda x: self.add_subtag_callback(x)) self.add_tag_modal = PanelModal( - tsp, + tsp, QCoreApplication.translate("build_tags", "add_parent_tags"), QCoreApplication.translate("build_tags", "add_parent_tags"), ) @@ -165,7 +167,12 @@ def __init__(self, library: Library, tag: Tag | None = None): # TODO - fill subtags self.subtags: set[int] = set() - self.set_tag(tag or Tag(name=QCoreApplication.translate("tag", "new"), )) + self.set_tag( + tag + or Tag( + name=QCoreApplication.translate("tag", "new"), + ) + ) def add_subtag_callback(self, tag_id: int): logger.info("add_subtag_callback", tag_id=tag_id) diff --git a/tagstudio/src/qt/modals/delete_unlinked.py b/tagstudio/src/qt/modals/delete_unlinked.py index 19a3af08f..346d5302f 100644 --- a/tagstudio/src/qt/modals/delete_unlinked.py +++ b/tagstudio/src/qt/modals/delete_unlinked.py @@ -4,7 +4,7 @@ import typing -from PySide6.QtCore import Signal, Qt, QThreadPool +from PySide6.QtCore import Signal, Qt, QThreadPool, QCoreApplication from PySide6.QtGui import QStandardItemModel, QStandardItem from PySide6.QtWidgets import ( QWidget, @@ -32,7 +32,9 @@ def __init__(self, driver: "QtDriver", tracker: MissingRegistry): super().__init__() self.driver = driver self.tracker = tracker - self.setWindowTitle("Delete Unlinked Entries") + self.setWindowTitle( + QCoreApplication.translate("delete_unlinked", "delete_unlinked") + ) self.setWindowModality(Qt.WindowModality.ApplicationModal) self.setMinimumSize(500, 400) self.root_layout = QVBoxLayout(self) @@ -41,9 +43,9 @@ 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? - """) + self.desc_widget.setText( + QCoreApplication.translate("delete_unlinked", "confirm") + ) self.desc_widget.setAlignment(Qt.AlignmentFlag.AlignCenter) self.list_view = QListView() @@ -56,13 +58,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(QCoreApplication.translate("generic", "cancel")) 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(QCoreApplication.translate("generic", "delete")) self.delete_button.clicked.connect(self.hide) self.delete_button.clicked.connect(lambda: self.delete_entries()) self.button_layout.addWidget(self.delete_button) @@ -72,9 +74,9 @@ 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? - """) + self.desc_widget.setText( + QCoreApplication.translate("delete_unlinked", "confirm") + ) self.model.clear() for i in self.tracker.missing_files: @@ -82,7 +84,9 @@ def refresh_list(self): def delete_entries(self): pw = ProgressWidget( - window_title="Deleting Entries", + window_title=QCoreApplication.translate( + "delete_unlinked", "delete_entries" + ), label_text="", cancel_button_text=None, minimum=0, @@ -91,11 +95,14 @@ def delete_entries(self): pw.show() iterator = FunctionIterator(self.tracker.execute_deletion) - files_count = self.tracker.missing_files_count iterator.value.connect( lambda idx: ( pw.update_progress(idx), - pw.update_label(f"Deleting {idx}/{files_count} Unlinked Entries"), + pw.update_label( + QCoreApplication.translate( + "delete_unlinked", "deleting_number_entries" + ) + ), ) ) diff --git a/tagstudio/src/qt/modals/file_extension.py b/tagstudio/src/qt/modals/file_extension.py index c631cd50b..14929881d 100644 --- a/tagstudio/src/qt/modals/file_extension.py +++ b/tagstudio/src/qt/modals/file_extension.py @@ -3,7 +3,7 @@ # Created for TagStudio: https://github.com/CyanVoxel/TagStudio -from PySide6.QtCore import Signal, Qt +from PySide6.QtCore import Signal, Qt, QCoreApplication from PySide6.QtWidgets import ( QVBoxLayout, QHBoxLayout, @@ -40,7 +40,7 @@ def __init__(self, library: "Library"): super().__init__() # Initialize Modal ===================================================== self.lib = library - self.setWindowTitle("File Extensions") + self.setWindowTitle() self.setWindowModality(Qt.WindowModality.ApplicationModal) self.setMinimumSize(240, 400) self.root_layout = QVBoxLayout(self) @@ -55,7 +55,9 @@ def __init__(self, library: "Library"): # Create "Add Button" Widget ------------------------------------------- self.add_button = QPushButton() - self.add_button.setText("&Add Extension") + self.add_button.setText( + QCoreApplication.translate("file_extension", "add_extension") + ) self.add_button.clicked.connect(self.add_item) self.add_button.setDefault(True) self.add_button.setMinimumWidth(100) @@ -66,11 +68,13 @@ 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( + QCoreApplication.translate("file_extension", "list_mode") + ) self.mode_combobox = QComboBox() self.mode_combobox.setEditable(False) - self.mode_combobox.addItem("Include") - self.mode_combobox.addItem("Exclude") + self.mode_combobox.addItem(QCoreApplication.translate("generic", "include")) + self.mode_combobox.addItem(QCoreApplication.translate("generic", "exclude")) is_exclude_list = int(bool(self.lib.prefs(LibraryPrefs.IS_EXCLUDE_LIST))) diff --git a/tagstudio/src/qt/translator.py b/tagstudio/src/qt/translator.py index 9997bfffd..175d895da 100644 --- a/tagstudio/src/qt/translator.py +++ b/tagstudio/src/qt/translator.py @@ -8,28 +8,27 @@ class TSTranslator(QTranslator): - def __init__(self, parent: QObject | None = None) -> None: - super().__init__(parent) - self.translations: dict[str, str] = {} + def __init__(self, parent: QObject | None = None) -> None: + super().__init__(parent) + self.translations: dict[str, str] = {} - def translate( - self, context, sourceText, disambiguation: str | None = None, n: int = -1 - ) -> str: - return self.translations.get(context + "." + sourceText.replace(" ", "")) + def translate( + self, context, sourceText, disambiguation: str | None = None, n: int = -1 + ) -> str: + return self.translations.get(context + "." + sourceText.replace(" ", "")) - def load(self, translationDir: Path, language: str, country: str = "") -> bool: - file = None - translations = None - if (translationDir / (language + "_" + country + ".json")).exists(): - file = open(translationDir / (language + "_" + country + ".json"), "r") + def load(self, translationDir: Path, language: str, country: str = "") -> bool: + file = None + translations = None + if (translationDir / (language + "_" + country + ".json")).exists(): + with open(translationDir / (language + "_" + country + ".json")) as file: translations = load_json(file) - elif (translationDir / (language + ".json")).exists(): - file = open(translationDir / (language + ".json"), "r") + elif (translationDir / (language + ".json")).exists(): + with open(translationDir / (language + ".json")) as file: translations = load_json(file) - # noqa: SIM115 - if file is None: - return False + if file is None: + return False - self.translations = translations - file.close() - return True + self.translations = translations + file.close() + return True From e41b48b0c3d25a6e7052d1c535058a60a8ca5c2e Mon Sep 17 00:00:00 2001 From: Coolio Date: Sun, 24 Nov 2024 15:39:31 -0600 Subject: [PATCH 8/9] Translate fix_dupes.py abd fix_unlinked.py --- tagstudio/src/qt/modals/fix_dupes.py | 28 ++++++++++++------------- tagstudio/src/qt/modals/fix_unlinked.py | 24 ++++++++++----------- 2 files changed, 25 insertions(+), 27 deletions(-) diff --git a/tagstudio/src/qt/modals/fix_dupes.py b/tagstudio/src/qt/modals/fix_dupes.py index e323c20e3..b7a100928 100644 --- a/tagstudio/src/qt/modals/fix_dupes.py +++ b/tagstudio/src/qt/modals/fix_dupes.py @@ -5,7 +5,7 @@ import typing -from PySide6.QtCore import Qt +from PySide6.QtCore import Qt, QCoreApplication from PySide6.QtWidgets import ( QWidget, QVBoxLayout, @@ -32,7 +32,7 @@ def __init__(self, library: "Library", driver: "QtDriver"): self.driver = driver self.count = -1 self.filename = "" - self.setWindowTitle("Fix Duplicate Files") + self.setWindowTitle(QCoreApplication.translate("fix_dupes", "fix_dupes")) self.setWindowModality(Qt.WindowModality.ApplicationModal) self.setMinimumSize(400, 300) self.root_layout = QVBoxLayout(self) @@ -78,22 +78,22 @@ def __init__(self, library: "Library", driver: "QtDriver"): # # 'padding-top: 6px' # '') # self.file_label.setAlignment(Qt.AlignmentFlag.AlignCenter) - self.file_label.setText("No DupeGuru File Selected") + self.file_label.setText(QCoreApplication.translate("fix_dupes", "no_file_selected")) self.open_button = QPushButton() - self.open_button.setText("&Load DupeGuru File") + self.open_button.setText(QCoreApplication.translate("fix_dupes", "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") + self.mirror_button.setText(QCoreApplication.translate("fix_dupes", "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.""" + QCoreApplication.translate("fix_dupes", "mirror_description") ) # self.mirror_delete_button = QPushButton() @@ -102,7 +102,7 @@ def __init__(self, library: "Library", driver: "QtDriver"): self.advice_label = QLabel() self.advice_label.setWordWrap(True) 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.""" + QCoreApplication.translate("fix_dupes", "advice_label") ) self.button_container = QWidget() @@ -111,7 +111,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(QCoreApplication.translate("generic", "done")) # self.save_button.setAutoDefault(True) self.done_button.setDefault(True) self.done_button.clicked.connect(self.hide) @@ -140,9 +140,9 @@ 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, QCoreApplication.translate("fix_dupes", "open_results_file"), str(self.lib.library_dir)) qfd.setFileMode(QFileDialog.FileMode.ExistingFile) - qfd.setNameFilter("DupeGuru Files (*.dupeguru)") + qfd.setNameFilter(QCoreApplication.translate("fix_dupes", "name_filter")) if qfd.exec_(): filename = qfd.selectedFiles() if filename: @@ -152,7 +152,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(QCoreApplication.translate("fix_dupes", "no_file_selected")) self.filename = filename self.refresh_dupes() self.mirror_modal.refresh_list() @@ -164,10 +164,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(QCoreApplication.translate("fix_dupes", "no_file_match")) elif count == 0: self.mirror_button.setDisabled(True) - self.dupe_count.setText(f"Duplicate File Matches: {count}") + self.dupe_count.setText(QCoreApplication.translate("fix_dupes", "number_file_match")) else: self.mirror_button.setDisabled(False) - self.dupe_count.setText(f"Duplicate File Matches: {count}") + self.dupe_count.setText(QCoreApplication.translate("fix_dupes", "number_file_match")) diff --git a/tagstudio/src/qt/modals/fix_unlinked.py b/tagstudio/src/qt/modals/fix_unlinked.py index b933aa6d3..4bba309b5 100644 --- a/tagstudio/src/qt/modals/fix_unlinked.py +++ b/tagstudio/src/qt/modals/fix_unlinked.py @@ -1,11 +1,11 @@ -# Copyright (C) 2024 Travis Abendshien (CyanVoxel). +z # Copyright (C) 2024 Travis Abendshien (CyanVoxel). # Licensed under the GPL-3.0 License. # Created for TagStudio: https://github.com/CyanVoxel/TagStudio import typing -from PySide6.QtCore import Qt, QThreadPool +from PySide6.QtCore import Qt, QThreadPool, QCoreApplication from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton from src.core.library import Library @@ -32,7 +32,7 @@ def __init__(self, library: "Library", driver: "QtDriver"): self.missing_count = -1 self.dupe_count = -1 - self.setWindowTitle("Fix Unlinked Entries") + self.setWindowTitle(QCoreApplication.translate("fix_unlinked", "fix_unlinked")) self.setWindowModality(Qt.WindowModality.ApplicationModal) self.setMinimumSize(400, 300) self.root_layout = QVBoxLayout(self) @@ -42,9 +42,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. Unlinked entries may be automatically relinked via searching your directories, manually relinked by the user, or deleted if desired.""" - ) + self.unlinked_desc_widget.setText(QCoreApplication.translate("fix_unlinked", "description")) self.missing_count_label = QLabel() self.missing_count_label.setObjectName("missingCountLabel") @@ -57,14 +55,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(QCoreApplication.translate("generic", "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") + self.search_button.setText(QCoreApplication.translate("fix_unlinked", "search_and_relink")) self.relink_class.done.connect( # refresh the grid lambda: ( @@ -75,7 +73,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(QCoreApplication.translate("fix_unlinked", "manual_relink")) self.manual_button.setHidden(True) self.delete_button = QPushButton() @@ -87,7 +85,7 @@ def __init__(self, library: "Library", driver: "QtDriver"): self.driver.filter_items(), ) ) - self.delete_button.setText("De&lete Unlinked Entries") + self.delete_button.setText(QCoreApplication.translate("fix_unlinked", "fix_unlinked")) self.delete_button.clicked.connect(self.delete_modal.show) self.button_container = QWidget() @@ -96,7 +94,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(QCoreApplication.translate("genric", "done")) self.done_button.setDefault(True) self.done_button.clicked.connect(self.hide) self.button_layout.addWidget(self.done_button) @@ -115,8 +113,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=QCoreApplication.translate("fix_unlinked", "scan_library.title"), + label_text=QCoreApplication.translate("fix_unlinked", "scan_library.label"), cancel_button_text=None, minimum=0, maximum=self.lib.entries_count, From a584693f88fceb181ecdbf88d771f5442c546e34 Mon Sep 17 00:00:00 2001 From: Coolio Date: Mon, 25 Nov 2024 18:22:44 -0600 Subject: [PATCH 9/9] Translate merge_dupe_entries.py and mirror_entities.py --- tagstudio/src/qt/modals/merge_dupe_entries.py | 6 ++--- tagstudio/src/qt/modals/mirror_entities.py | 23 ++++++++----------- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/tagstudio/src/qt/modals/merge_dupe_entries.py b/tagstudio/src/qt/modals/merge_dupe_entries.py index 470512334..415cf3f7e 100644 --- a/tagstudio/src/qt/modals/merge_dupe_entries.py +++ b/tagstudio/src/qt/modals/merge_dupe_entries.py @@ -4,7 +4,7 @@ import typing -from PySide6.QtCore import QObject, Signal, QThreadPool +from PySide6.QtCore import QObject, Signal, QThreadPool, QCoreApplication from src.core.library import Library from src.core.utils.dupe_files import DupeRegistry @@ -30,7 +30,7 @@ def merge_entries(self): iterator = FunctionIterator(self.tracker.merge_dupe_entries) pw = ProgressWidget( - window_title="Merging Duplicate Entries", + window_title=QCoreApplication.translate("merge", "window_title"), label_text="", cancel_button_text=None, minimum=0, @@ -40,7 +40,7 @@ def merge_entries(self): iterator.value.connect(lambda x: pw.update_progress(x)) iterator.value.connect( - lambda: (pw.update_label("Merging Duplicate Entries...")) + lambda: (pw.update_label(QCoreApplication.translate("merge", "merge_dupe_entries")) ) r = CustomRunnable(iterator.run) diff --git a/tagstudio/src/qt/modals/mirror_entities.py b/tagstudio/src/qt/modals/mirror_entities.py index 97a11357b..89cf5e1b6 100644 --- a/tagstudio/src/qt/modals/mirror_entities.py +++ b/tagstudio/src/qt/modals/mirror_entities.py @@ -6,7 +6,7 @@ from time import sleep import typing -from PySide6.QtCore import Signal, Qt, QThreadPool +from PySide6.QtCore import Signal, Qt, QThreadPool, QCoreApplication from PySide6.QtGui import QStandardItemModel, QStandardItem from PySide6.QtWidgets import ( QWidget, @@ -33,7 +33,7 @@ class MirrorEntriesModal(QWidget): def __init__(self, driver: "QtDriver", tracker: DupeRegistry): super().__init__() self.driver = driver - self.setWindowTitle("Mirror Entries") + self.setWindowTitle(QCoreApplication.translate("fix_dupes", "mirror_entries")) self.setWindowModality(Qt.WindowModality.ApplicationModal) self.setMinimumSize(500, 400) self.root_layout = QVBoxLayout(self) @@ -44,9 +44,7 @@ def __init__(self, driver: "QtDriver", tracker: DupeRegistry): 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? - """) + self.desc_widget.setText(QCoreApplication.translate("mirror_entities", "are_you_sure")) self.desc_widget.setAlignment(Qt.AlignmentFlag.AlignCenter) self.list_view = QListView() @@ -59,13 +57,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(QCoreApplication.translate("generic", "cancel")) 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(QCoreApplication.translate("generic", "mirror")) self.mirror_button.clicked.connect(self.hide) self.mirror_button.clicked.connect(self.mirror_entries) self.button_layout.addWidget(self.mirror_button) @@ -75,9 +73,7 @@ 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(QCoreApplication.translate("mirror_entities", "are_you_sure")) self.model.clear() for i in self.tracker.groups: @@ -86,8 +82,8 @@ def refresh_list(self): def mirror_entries(self): iterator = FunctionIterator(self.mirror_entries_runnable) pw = ProgressWidget( - window_title="Mirroring Entries", - label_text=f"Mirroring 1/{self.tracker.groups_count} Entries...", + window_title=QCoreApplication.translate("mirror_entities", "title"), + label_text=QCoreApplication.translate("mirror_entities", "label"), cancel_button_text=None, minimum=0, maximum=self.tracker.groups_count, @@ -96,8 +92,7 @@ def mirror_entries(self): iterator.value.connect(lambda x: pw.update_progress(x + 1)) iterator.value.connect( lambda x: pw.update_label( - f"Mirroring {x + 1}/{self.tracker.groups_count} Entries..." - ) + QCoreApplication.translate("mirror_entities", "label")), ) r = CustomRunnable(iterator.run) QThreadPool.globalInstance().start(r)