diff --git a/tagstudio/src/qt/modals/about.py b/tagstudio/src/qt/modals/about.py index 8d921ad5..da077aca 100644 --- a/tagstudio/src/qt/modals/about.py +++ b/tagstudio/src/qt/modals/about.py @@ -44,7 +44,8 @@ class AboutModal(QWidget): if ff_version["ffprobe"] is not None: ffprobe = 'Found (' + ff_version["ffprobe"] + ")" self.content_widget = QLabel( - Translations["about.content"].format( + Translations.format( + "about.content", version=VERSION, branch=VERSION_BRANCH, config_path=config_path, diff --git a/tagstudio/src/qt/modals/delete_unlinked.py b/tagstudio/src/qt/modals/delete_unlinked.py index c038b7b4..a8a88102 100644 --- a/tagstudio/src/qt/modals/delete_unlinked.py +++ b/tagstudio/src/qt/modals/delete_unlinked.py @@ -32,7 +32,8 @@ class DeleteUnlinkedEntriesModal(QWidget): self.root_layout.setContentsMargins(6, 6, 6, 6) self.desc_widget = QLabel( - Translations["entries.unlinked.delete.confirm"].format( + Translations.format( + "entries.unlinked.delete.confirm", count=self.tracker.missing_file_entries_count, ) ) @@ -65,8 +66,8 @@ class DeleteUnlinkedEntriesModal(QWidget): def refresh_list(self): self.desc_widget.setText( - Translations["entries.unlinked.delete.confirm"].format( - count=self.tracker.missing_file_entries_count + Translations.format( + "entries.unlinked.delete.confirm", count=self.tracker.missing_file_entries_count ) ) diff --git a/tagstudio/src/qt/modals/drop_import.py b/tagstudio/src/qt/modals/drop_import.py index d0d1daff..fcc84178 100644 --- a/tagstudio/src/qt/modals/drop_import.py +++ b/tagstudio/src/qt/modals/drop_import.py @@ -131,8 +131,8 @@ class DropImportModal(QWidget): self.desc_widget.setText( Translations["drop_import.duplicates_choice.singular"] if len(self.duplicate_files) == 1 - else Translations["drop_import.duplicates_choice.plural"].format( - count=len(self.duplicate_files) + else Translations.format( + "drop_import.duplicates_choice.plural", count=len(self.duplicate_files) ) ) @@ -154,11 +154,10 @@ class DropImportModal(QWidget): return def displayed_text(x): - return Translations[ + return Translations.format( "drop_import.progress.label.singular" if x[0] + 1 == 1 - else "drop_import.progress.label.plural" - ].format( + else "drop_import.progress.label.plural", count=x[0] + 1, suffix=f" {x[1]} {self.choice.value}" if self.choice else "", ) diff --git a/tagstudio/src/qt/modals/fix_dupes.py b/tagstudio/src/qt/modals/fix_dupes.py index db997e31..78b57438 100644 --- a/tagstudio/src/qt/modals/fix_dupes.py +++ b/tagstudio/src/qt/modals/fix_dupes.py @@ -114,10 +114,10 @@ class FixDupeFilesModal(QWidget): self.dupe_count.setText(Translations["file.duplicates.matches_uninitialized"]) elif count == 0: self.mirror_button.setDisabled(True) - self.dupe_count.setText(Translations["file.duplicates.matches"].format(count=count)) + self.dupe_count.setText(Translations.format("file.duplicates.matches", count=count)) else: self.mirror_button.setDisabled(False) - self.dupe_count.setText(Translations["file.duplicates.matches"].format(count=count)) + self.dupe_count.setText(Translations.format("file.duplicates.matches", count=count)) @override def keyPressEvent(self, event: QtGui.QKeyEvent) -> None: # noqa N802 diff --git a/tagstudio/src/qt/modals/fix_unlinked.py b/tagstudio/src/qt/modals/fix_unlinked.py index 4b3dd9d2..aa301644 100644 --- a/tagstudio/src/qt/modals/fix_unlinked.py +++ b/tagstudio/src/qt/modals/fix_unlinked.py @@ -135,7 +135,7 @@ class FixUnlinkedEntriesModal(QWidget): self.search_button.setDisabled(self.missing_count == 0) self.delete_button.setDisabled(self.missing_count == 0) self.missing_count_label.setText( - Translations["entries.unlinked.missing_count.some"].format(count=self.missing_count) + Translations.format("entries.unlinked.missing_count.some", count=self.missing_count) ) @override diff --git a/tagstudio/src/qt/modals/mirror_entities.py b/tagstudio/src/qt/modals/mirror_entities.py index 2096af57..4b7390e4 100644 --- a/tagstudio/src/qt/modals/mirror_entities.py +++ b/tagstudio/src/qt/modals/mirror_entities.py @@ -32,7 +32,7 @@ class MirrorEntriesModal(QWidget): self.tracker = tracker self.desc_widget = QLabel( - Translations["entries.mirror.confirmation"].format(count=self.tracker.groups_count) + Translations.format("entries.mirror.confirmation", count=self.tracker.groups_count) ) self.desc_widget.setObjectName("descriptionLabel") self.desc_widget.setWordWrap(True) @@ -63,7 +63,7 @@ class MirrorEntriesModal(QWidget): def refresh_list(self): self.desc_widget.setText( - Translations["entries.mirror.confirmation"].format(count=self.tracker.groups_count) + Translations.format("entries.mirror.confirmation", count=self.tracker.groups_count) ) self.model.clear() @@ -72,8 +72,8 @@ class MirrorEntriesModal(QWidget): def mirror_entries(self): def displayed_text(x): - return Translations["entries.mirror.label"].format( - idx=x + 1, count=self.tracker.groups_count + return Translations.format( + "entries.mirror.label", idx=x + 1, count=self.tracker.groups_count ) pw = ProgressWidget( diff --git a/tagstudio/src/qt/modals/relink_unlinked.py b/tagstudio/src/qt/modals/relink_unlinked.py index 51b88dee..7fec73aa 100644 --- a/tagstudio/src/qt/modals/relink_unlinked.py +++ b/tagstudio/src/qt/modals/relink_unlinked.py @@ -18,7 +18,8 @@ class RelinkUnlinkedEntries(QObject): def repair_entries(self): def displayed_text(x): - return Translations["entries.unlinked.relink.attempting"].format( + return Translations.format( + "entries.unlinked.relink.attempting", idx=x, missing_count=self.tracker.missing_file_entries_count, fixed_count=self.tracker.files_fixed_count, diff --git a/tagstudio/src/qt/modals/tag_database.py b/tagstudio/src/qt/modals/tag_database.py index dba82cfa..293b9c4e 100644 --- a/tagstudio/src/qt/modals/tag_database.py +++ b/tagstudio/src/qt/modals/tag_database.py @@ -60,9 +60,7 @@ class TagDatabasePanel(TagSearchPanel): message_box = QMessageBox( QMessageBox.Question, # type: ignore Translations["tag.remove"], - Translations["tag.confirm_delete"].format( - tag_name=self.lib.tag_display_name(tag.id), - ), + Translations.format("tag.confirm_delete", tag_name=self.lib.tag_display_name(tag.id)), QMessageBox.Ok | QMessageBox.Cancel, # type: ignore ) diff --git a/tagstudio/src/qt/modals/tag_search.py b/tagstudio/src/qt/modals/tag_search.py index fa115b39..1c1ae9e1 100644 --- a/tagstudio/src/qt/modals/tag_search.py +++ b/tagstudio/src/qt/modals/tag_search.py @@ -118,9 +118,9 @@ class TagSearchPanel(PanelWidget): """Set the QtDriver for this search panel. Used for main window operations.""" self.driver = driver - def build_create_button(self, query: str | None, key: str, format_args: dict): + def build_create_button(self, query: str | None): """Constructs a "Create & Add Tag" QPushButton.""" - create_button = QPushButton(Translations[key].format(**format_args), self) + create_button = QPushButton(self) create_button.setFlat(True) create_button.setMinimumSize(22, 22) @@ -244,7 +244,8 @@ class TagSearchPanel(PanelWidget): # Add back the "Create & Add" button if query and query.strip(): - cb: QPushButton = self.build_create_button(query, "tag.create_add", {"query": query}) + cb: QPushButton = self.build_create_button(query) + cb.setText(Translations.format("tag.create_add", query=query)) with catch_warnings(record=True): cb.clicked.disconnect() cb.clicked.connect(lambda: self.create_and_add_tag(query or "")) diff --git a/tagstudio/src/qt/translations.py b/tagstudio/src/qt/translations.py index 6b4eb33f..b47e4ae1 100644 --- a/tagstudio/src/qt/translations.py +++ b/tagstudio/src/qt/translations.py @@ -1,4 +1,6 @@ +from collections import defaultdict from pathlib import Path +from typing import Any import structlog import ujson @@ -27,6 +29,23 @@ class Translator: self._lang = lang self._strings = self.__get_translation_dict(lang) + def __format(self, text: str, **kwargs) -> str: + try: + return text.format(**kwargs) + except (KeyError, ValueError): + logger.error( + "[Translations] Error while formatting translation.", + text=text, + kwargs=kwargs, + language=self._lang, + ) + params: defaultdict[str, Any] = defaultdict(lambda: "{unknown_key}") + params.update(kwargs) + return text.format_map(params) + + def format(self, key: str, **kwargs) -> str: + return self.__format(self[key], **kwargs) + def __getitem__(self, key: str) -> str: return self._strings.get(key) or self._default_strings.get(key) or f"[{key}]" diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index 93422984..0d22e034 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -496,7 +496,7 @@ class QtDriver(DriverMixin, QObject): edit_menu.addSeparator() self.delete_file_action = QAction( - Translations["menu.delete_selected_files_ambiguous"].format(trash_term=trash_term()), + Translations.format("menu.delete_selected_files_ambiguous", trash_term=trash_term()), menu_bar, ) self.delete_file_action.triggered.connect(lambda f="": self.delete_files_callback(f)) @@ -876,8 +876,8 @@ class QtDriver(DriverMixin, QObject): end_time = time.time() self.main_window.statusbar.showMessage( - Translations["status.library_closed"].format( - time_span=format_timespan(end_time - start_time) + Translations.format( + "status.library_closed", time_span=format_timespan(end_time - start_time) ) ) @@ -888,7 +888,8 @@ class QtDriver(DriverMixin, QObject): target_path = self.lib.save_library_backup_to_disk() end_time = time.time() self.main_window.statusbar.showMessage( - Translations["status.library_backup_success"].format( + Translations.format( + "status.library_backup_success", path=target_path, time_span=format_timespan(end_time - start_time), ) @@ -986,8 +987,8 @@ class QtDriver(DriverMixin, QObject): self.preview_panel.thumb.stop_file_use() if delete_file(self.lib.library_dir / f): self.main_window.statusbar.showMessage( - Translations["status.deleting_file"].format( - i=i, count=len(pending), path=f + Translations.format( + "status.deleting_file", i=i, count=len(pending), path=f ) ) self.main_window.statusbar.repaint() @@ -1004,17 +1005,17 @@ class QtDriver(DriverMixin, QObject): self.main_window.statusbar.showMessage(Translations["status.deleted_none"]) elif len(self.selected) <= 1 and deleted_count == 1: self.main_window.statusbar.showMessage( - Translations["status.deleted_file_plural"].format(count=deleted_count) + Translations.format("status.deleted_file_plural", count=deleted_count) ) elif len(self.selected) > 1 and deleted_count == 0: self.main_window.statusbar.showMessage(Translations["status.deleted_none"]) elif len(self.selected) > 1 and deleted_count < len(self.selected): self.main_window.statusbar.showMessage( - Translations["status.deleted_partial_warning"].format(count=deleted_count) + Translations.format("status.deleted_partial_warning", count=deleted_count) ) elif len(self.selected) > 1 and deleted_count == len(self.selected): self.main_window.statusbar.showMessage( - Translations["status.deleted_file_plural"].format(count=deleted_count) + Translations.format("status.deleted_file_plural", count=deleted_count) ) self.main_window.statusbar.repaint() @@ -1031,8 +1032,8 @@ class QtDriver(DriverMixin, QObject): # https://github.com/arsenetar/send2trash/issues/28 # This warning is applied to all platforms until at least macOS and Linux can be verified # to not exhibit this same behavior. - perm_warning_msg = Translations["trash.dialog.permanent_delete_warning"].format( - trash_term=trash_term() + perm_warning_msg = Translations.format( + "trash.dialog.permanent_delete_warning", trash_term=trash_term() ) perm_warning: str = ( f"