From 7e75087b243634cc07b2b2827c867be0e2aebb2b Mon Sep 17 00:00:00 2001 From: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com> Date: Thu, 6 Mar 2025 13:05:56 -0800 Subject: [PATCH] fix: restore `translate_formatted()` method as `format()` (#830) * fix: restore `translate_formatted()` method * fix: set "Create & Add" button text * refactor: rename "translate_formatted" to "format" * translations: replace invalid format key names with "{unknown_key}" --- tagstudio/src/qt/modals/about.py | 3 +- tagstudio/src/qt/modals/delete_unlinked.py | 7 +-- tagstudio/src/qt/modals/drop_import.py | 9 ++-- tagstudio/src/qt/modals/fix_dupes.py | 4 +- tagstudio/src/qt/modals/fix_unlinked.py | 2 +- tagstudio/src/qt/modals/mirror_entities.py | 8 +-- tagstudio/src/qt/modals/relink_unlinked.py | 3 +- tagstudio/src/qt/modals/tag_database.py | 4 +- tagstudio/src/qt/modals/tag_search.py | 7 +-- tagstudio/src/qt/translations.py | 19 +++++++ tagstudio/src/qt/ts_qt.py | 52 ++++++++++--------- tagstudio/src/qt/widgets/color_box.py | 2 +- tagstudio/src/qt/widgets/item_thumb.py | 3 +- tagstudio/src/qt/widgets/landing.py | 2 +- tagstudio/src/qt/widgets/migration_modal.py | 6 +-- .../qt/widgets/preview/field_containers.py | 2 +- .../src/qt/widgets/preview/file_attributes.py | 2 +- .../src/qt/widgets/preview/preview_thumb.py | 5 +- 18 files changed, 83 insertions(+), 57 deletions(-) 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"

" @@ -1049,8 +1050,8 @@ class QtDriver(DriverMixin, QObject): ) msg.setIcon(QMessageBox.Icon.Warning) if count <= 1: - msg_text = Translations["trash.dialog.move.confirmation.singular"].format( - trash_term=trash_term() + msg_text = Translations.format( + "trash.dialog.move.confirmation.singular", trash_term=trash_term() ) msg.setText( f"

{msg_text}

" @@ -1059,7 +1060,8 @@ class QtDriver(DriverMixin, QObject): f"{perm_warning}
" ) elif count > 1: - msg_text = Translations["trash.dialog.move.confirmation.plural"].format( + msg_text = Translations.format( + "trash.dialog.move.confirmation.plural", count=count, trash_term=trash_term(), ) @@ -1094,11 +1096,10 @@ class QtDriver(DriverMixin, QObject): lambda x: ( pw.update_progress(x + 1), pw.update_label( - Translations[ + Translations.format( "library.refresh.scanning.plural" if x + 1 != 1 - else "library.refresh.scanning.singular" - ].format( + else "library.refresh.scanning.singular", searched_count=f"{x+1:n}", found_count=f"{tracker.files_count:n}", ) @@ -1130,15 +1131,15 @@ class QtDriver(DriverMixin, QObject): ) pw.setWindowTitle(Translations["entries.running.dialog.title"]) pw.update_label( - Translations["entries.running.dialog.new_entries"].format(total=f"{files_count:n}") + Translations.format("entries.running.dialog.new_entries", total=f"{files_count:n}") ) pw.show() iterator.value.connect( lambda: ( pw.update_label( - Translations["entries.running.dialog.new_entries"].format( - total=f"{files_count:n}" + Translations.format( + "entries.running.dialog.new_entries", total=f"{files_count:n}" ) ), ) @@ -1698,7 +1699,8 @@ class QtDriver(DriverMixin, QObject): # inform user about completed search self.main_window.statusbar.showMessage( - Translations["status.results_found"].format( + Translations.format( + "status.results_found", count=results.total_count, time_span=format_timespan(end_time - start_time), ) @@ -1824,7 +1826,7 @@ class QtDriver(DriverMixin, QObject): def open_library(self, path: Path) -> None: """Open a TagStudio library.""" - message = Translations["splash.opening_library"].format(library_path=str(path)) + message = Translations.format("splash.opening_library", library_path=str(path)) self.main_window.landing_widget.set_status_label(message) self.main_window.statusbar.showMessage(message, 3) self.main_window.repaint() @@ -1871,8 +1873,10 @@ class QtDriver(DriverMixin, QObject): self.update_libs_list(path) self.main_window.setWindowTitle( - Translations["app.title"].format( - base_title=self.base_title, library_dir=self.lib.library_dir + Translations.format( + "app.title", + base_title=self.base_title, + library_dir=self.lib.library_dir, ) ) self.main_window.setAcceptDrops(True) diff --git a/tagstudio/src/qt/widgets/color_box.py b/tagstudio/src/qt/widgets/color_box.py index da5fb597..3c20c41e 100644 --- a/tagstudio/src/qt/widgets/color_box.py +++ b/tagstudio/src/qt/widgets/color_box.py @@ -146,7 +146,7 @@ class ColorBoxWidget(FieldWidget): message_box = QMessageBox( QMessageBox.Icon.Warning, Translations["color.delete"], - Translations["color.confirm_delete"].format(color_name=color_group.name), + Translations.format("color.confirm_delete", color_name=color_group.name), ) cancel_button = message_box.addButton( Translations["generic.cancel_alt"], QMessageBox.ButtonRole.RejectRole diff --git a/tagstudio/src/qt/widgets/item_thumb.py b/tagstudio/src/qt/widgets/item_thumb.py index dc637073..d1f45752 100644 --- a/tagstudio/src/qt/widgets/item_thumb.py +++ b/tagstudio/src/qt/widgets/item_thumb.py @@ -222,7 +222,8 @@ class ItemThumb(FlowWidget): open_explorer_action.triggered.connect(self.opener.open_explorer) self.delete_action = QAction( - Translations["trash.context.ambiguous"].format(trash_term=trash_term()), self + Translations.format("trash.context.ambiguous", trash_term=trash_term()), + self, ) self.thumb_button.addAction(open_file_action) diff --git a/tagstudio/src/qt/widgets/landing.py b/tagstudio/src/qt/widgets/landing.py index f584a320..111f5daa 100644 --- a/tagstudio/src/qt/widgets/landing.py +++ b/tagstudio/src/qt/widgets/landing.py @@ -62,7 +62,7 @@ class LandingWidget(QWidget): else: open_shortcut_text = "(Ctrl+O)" self.open_button: QPushButton = QPushButton( - Translations["landing.open_create_library"].format(shortcut=open_shortcut_text) + Translations.format("landing.open_create_library", shortcut=open_shortcut_text) ) self.open_button.setMinimumWidth(200) self.open_button.clicked.connect(self.driver.open_library_from_dialog) diff --git a/tagstudio/src/qt/widgets/migration_modal.py b/tagstudio/src/qt/widgets/migration_modal.py index 09fd2845..9ad16a88 100644 --- a/tagstudio/src/qt/widgets/migration_modal.py +++ b/tagstudio/src/qt/widgets/migration_modal.py @@ -57,7 +57,7 @@ class JsonMigrationModal(QObject): self.is_migration_initialized: bool = False self.discrepancies: list[str] = [] - self.title: str = Translations["json_migration.title"].format(path=self.path) + self.title: str = Translations.format("json_migration.title", path=self.path) self.warning: str = "(!)" self.old_entry_count: int = 0 @@ -405,8 +405,8 @@ class JsonMigrationModal(QObject): 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) - yield Translations["json_migration.migrating_files_entries"].format( - entries=len(self.json_lib.entries) + yield Translations.format( + "json_migration.migrating_files_entries", entries=len(self.json_lib.entries) ) self.sql_lib.migrate_json_to_sqlite(self.json_lib) yield Translations["json_migration.checking_for_parity"] diff --git a/tagstudio/src/qt/widgets/preview/field_containers.py b/tagstudio/src/qt/widgets/preview/field_containers.py index a6d42232..4e732199 100644 --- a/tagstudio/src/qt/widgets/preview/field_containers.py +++ b/tagstudio/src/qt/widgets/preview/field_containers.py @@ -246,7 +246,7 @@ class FieldContainers(QWidget): return cats def remove_field_prompt(self, name: str) -> str: - return Translations["library.field.confirm_remove"].format(name=name) + return Translations.format("library.field.confirm_remove", name=name) def add_field_to_selected(self, field_list: list): """Add list of entry fields to one or more selected items. diff --git a/tagstudio/src/qt/widgets/preview/file_attributes.py b/tagstudio/src/qt/widgets/preview/file_attributes.py index c6254650..dd309735 100644 --- a/tagstudio/src/qt/widgets/preview/file_attributes.py +++ b/tagstudio/src/qt/widgets/preview/file_attributes.py @@ -231,7 +231,7 @@ class FileAttributes(QWidget): """Format attributes for multiple selected items.""" self.layout().setSpacing(0) self.file_label.setAlignment(Qt.AlignmentFlag.AlignCenter) - self.file_label.setText(Translations["preview.multiple_selection"].format(count=count)) + self.file_label.setText(Translations.format("preview.multiple_selection", count=count)) self.file_label.setCursor(Qt.CursorShape.ArrowCursor) self.file_label.set_file_path("") self.dimensions_label.setText("") diff --git a/tagstudio/src/qt/widgets/preview/preview_thumb.py b/tagstudio/src/qt/widgets/preview/preview_thumb.py index 38a89ac0..e1f2443c 100644 --- a/tagstudio/src/qt/widgets/preview/preview_thumb.py +++ b/tagstudio/src/qt/widgets/preview/preview_thumb.py @@ -57,7 +57,8 @@ class PreviewThumb(QWidget): self.open_file_action = QAction(Translations["file.open_file"], self) self.open_explorer_action = QAction(open_file_str(), self) self.delete_action = QAction( - Translations["trash.context.ambiguous"].format(trash_term=trash_term()), self + Translations.format("trash.context.ambiguous", trash_term=trash_term()), + self, ) self.preview_img = QPushButtonWrapper() @@ -378,7 +379,7 @@ class PreviewThumb(QWidget): self.delete_action.triggered.disconnect() self.delete_action.setText( - Translations["trash.context.singular"].format(trash_term=trash_term()) + Translations.format("trash.context.singular", trash_term=trash_term()) ) self.delete_action.triggered.connect( lambda checked=False, f=filepath: self.driver.delete_files_callback(f)