diff --git a/src/tagstudio/qt/mixed/color_box.py b/src/tagstudio/qt/mixed/color_box.py index 3a031f2ac..65e7aa2ff 100644 --- a/src/tagstudio/qt/mixed/color_box.py +++ b/src/tagstudio/qt/mixed/color_box.py @@ -120,7 +120,7 @@ def set_colors(self, colors: Iterable[TagColorGroup]): TagColorGroup( slug="slug", namespace=self.namespace, - name="Color", + name=Translations["color.placeholder"], primary="#FFFFFF", secondary=None, ) diff --git a/src/tagstudio/qt/mixed/field_containers.py b/src/tagstudio/qt/mixed/field_containers.py index 7a64419f7..1c489d197 100644 --- a/src/tagstudio/qt/mixed/field_containers.py +++ b/src/tagstudio/qt/mixed/field_containers.py @@ -485,12 +485,12 @@ def update_datetime_field(self, field: DatetimeField, value: str): def remove_message_box(self, prompt: str, callback: Callable) -> None: remove_mb = QMessageBox() remove_mb.setText(prompt) - remove_mb.setWindowTitle("Remove Field") # TODO: Localize + remove_mb.setWindowTitle(Translations["library.field.remove"]) remove_mb.setIcon(QMessageBox.Icon.Warning) cancel_button = remove_mb.addButton( Translations["generic.cancel_alt"], QMessageBox.ButtonRole.DestructiveRole ) - remove_mb.addButton("&Remove", QMessageBox.ButtonRole.RejectRole) + remove_mb.addButton(Translations["generic.remove_alt"], QMessageBox.ButtonRole.RejectRole) remove_mb.setEscapeButton(cancel_button) result = remove_mb.exec_() if result == QMessageBox.ButtonRole.ActionRole.value: diff --git a/src/tagstudio/qt/mixed/folders_to_tags.py b/src/tagstudio/qt/mixed/folders_to_tags.py index 45cdbc0a3..ce1e0718e 100644 --- a/src/tagstudio/qt/mixed/folders_to_tags.py +++ b/src/tagstudio/qt/mixed/folders_to_tags.py @@ -186,10 +186,7 @@ def __init__(self, library: "Library", driver: "QtDriver"): self.desc_widget = QLabel() self.desc_widget.setObjectName("descriptionLabel") self.desc_widget.setWordWrap(True) - self.desc_widget.setText( - """Creates tags based on your folder structure and applies them to your entries. - This tree shows all tags to be created and which entries they will be applied to.""" - ) + self.desc_widget.setText(Translations["folders_to_tags.description"]) self.desc_widget.setAlignment(Qt.AlignmentFlag.AlignCenter) self.open_close_button_w = QWidget() diff --git a/src/tagstudio/qt/mixed/settings_panel.py b/src/tagstudio/qt/mixed/settings_panel.py index 5e8c022db..3e95574a8 100644 --- a/src/tagstudio/qt/mixed/settings_panel.py +++ b/src/tagstudio/qt/mixed/settings_panel.py @@ -38,31 +38,49 @@ class SettingsPanel(PanelWidget): driver: "QtDriver" - filepath_option_map: dict[ShowFilepathOption, str] = { - ShowFilepathOption.SHOW_FULL_PATHS: Translations["settings.filepath.option.full"], - ShowFilepathOption.SHOW_RELATIVE_PATHS: Translations["settings.filepath.option.relative"], - ShowFilepathOption.SHOW_FILENAMES_ONLY: Translations["settings.filepath.option.name"], - } + @staticmethod + def filepath_option_map() -> dict[ShowFilepathOption, str]: + return { + ShowFilepathOption.SHOW_FULL_PATHS: Translations["settings.filepath.option.full"], + ShowFilepathOption.SHOW_RELATIVE_PATHS: Translations[ + "settings.filepath.option.relative" + ], + ShowFilepathOption.SHOW_FILENAMES_ONLY: Translations[ + "settings.filepath.option.name" + ], + } - theme_map: dict[Theme, str] = { - Theme.SYSTEM: Translations["settings.theme.system"], - Theme.DARK: Translations["settings.theme.dark"], - Theme.LIGHT: Translations["settings.theme.light"], - } + @staticmethod + def theme_map() -> dict[Theme, str]: + return { + Theme.SYSTEM: Translations["settings.theme.system"], + Theme.DARK: Translations["settings.theme.dark"], + Theme.LIGHT: Translations["settings.theme.light"], + } - splash_map: dict[Splash, str] = { - Splash.DEFAULT: Translations["settings.splash.option.default"], - Splash.RANDOM: Translations["settings.splash.option.random"], - Splash.CLASSIC: Translations["settings.splash.option.classic"], - Splash.GOO_GEARS: Translations["settings.splash.option.goo_gears"], - Splash.NINETY_FIVE: Translations["settings.splash.option.ninety_five"], - } + @staticmethod + def splash_map() -> dict[Splash, str]: + return { + Splash.DEFAULT: Translations["settings.splash.option.default"], + Splash.RANDOM: Translations["settings.splash.option.random"], + Splash.CLASSIC: Translations["settings.splash.option.classic"], + Splash.GOO_GEARS: Translations["settings.splash.option.goo_gears"], + Splash.NINETY_FIVE: Translations["settings.splash.option.ninety_five"], + } - tag_click_action_map: dict[TagClickActionOption, str] = { - TagClickActionOption.OPEN_EDIT: Translations["settings.tag_click_action.open_edit"], - TagClickActionOption.SET_SEARCH: Translations["settings.tag_click_action.set_search"], - TagClickActionOption.ADD_TO_SEARCH: Translations["settings.tag_click_action.add_to_search"], - } + @staticmethod + def tag_click_action_map() -> dict[TagClickActionOption, str]: + return { + TagClickActionOption.OPEN_EDIT: Translations[ + "settings.tag_click_action.open_edit" + ], + TagClickActionOption.SET_SEARCH: Translations[ + "settings.tag_click_action.set_search" + ], + TagClickActionOption.ADD_TO_SEARCH: Translations[ + "settings.tag_click_action.add_to_search" + ], + } date_format_map: dict[str, str] = { "%d/%m/%y": "21/08/24", @@ -84,9 +102,6 @@ class SettingsPanel(PanelWidget): def __init__(self, driver: "QtDriver"): super().__init__() - # set these "constants" because language will be loaded from config shortly after startup - # and we want to use the current language for the dropdowns - self.driver = driver self.setMinimumSize(400, 500) @@ -212,25 +227,27 @@ def on_page_size_changed(): # Show Filepath self.filepath_combobox = QComboBox() - for k in SettingsPanel.filepath_option_map: - self.filepath_combobox.addItem(SettingsPanel.filepath_option_map[k], k) + filepath_option_map = SettingsPanel.filepath_option_map() + for k, label in filepath_option_map.items(): + self.filepath_combobox.addItem(label, k) filepath_option: ShowFilepathOption = self.driver.settings.show_filepath - if filepath_option not in SettingsPanel.filepath_option_map: + if filepath_option not in filepath_option_map: filepath_option = ShowFilepathOption.DEFAULT self.filepath_combobox.setCurrentIndex( - list(SettingsPanel.filepath_option_map.keys()).index(filepath_option) + list(filepath_option_map.keys()).index(filepath_option) ) form_layout.addRow(Translations["settings.filepath.label"], self.filepath_combobox) # Tag Click Action self.tag_click_action_combobox = QComboBox() - for k in SettingsPanel.tag_click_action_map: - self.tag_click_action_combobox.addItem(SettingsPanel.tag_click_action_map[k], k) + tag_click_action_map = SettingsPanel.tag_click_action_map() + for k, label in tag_click_action_map.items(): + self.tag_click_action_combobox.addItem(label, k) tag_click_action = self.driver.settings.tag_click_action - if tag_click_action not in SettingsPanel.tag_click_action_map: + if tag_click_action not in tag_click_action_map: tag_click_action = TagClickActionOption.DEFAULT self.tag_click_action_combobox.setCurrentIndex( - list(SettingsPanel.tag_click_action_map.keys()).index(tag_click_action) + list(tag_click_action_map.keys()).index(tag_click_action) ) form_layout.addRow( Translations["settings.tag_click_action.label"], self.tag_click_action_combobox @@ -238,23 +255,25 @@ def on_page_size_changed(): # Dark Mode self.theme_combobox = QComboBox() - for k in SettingsPanel.theme_map: - self.theme_combobox.addItem(SettingsPanel.theme_map[k], k) + theme_map = SettingsPanel.theme_map() + for k, label in theme_map.items(): + self.theme_combobox.addItem(label, k) theme = self.driver.settings.theme - if theme not in SettingsPanel.theme_map: + if theme not in theme_map: theme = Theme.DEFAULT - self.theme_combobox.setCurrentIndex(list(SettingsPanel.theme_map.keys()).index(theme)) + self.theme_combobox.setCurrentIndex(list(theme_map.keys()).index(theme)) self.theme_combobox.currentIndexChanged.connect(self.__update_restart_label) form_layout.addRow(Translations["settings.theme.label"], self.theme_combobox) # Splash Screen self.splash_combobox = QComboBox() - for k in SettingsPanel.splash_map: - self.splash_combobox.addItem(SettingsPanel.splash_map[k], k) + splash_map = SettingsPanel.splash_map() + for k, label in splash_map.items(): + self.splash_combobox.addItem(label, k) splash = self.driver.settings.splash - if splash not in SettingsPanel.splash_map: + if splash not in splash_map: splash = Splash.DEFAULT - self.splash_combobox.setCurrentIndex(list(SettingsPanel.splash_map.keys()).index(splash)) + self.splash_combobox.setCurrentIndex(list(splash_map.keys()).index(splash)) form_layout.addRow(Translations["settings.splash.label"], self.splash_combobox) # Date Format diff --git a/src/tagstudio/qt/mixed/tag_database.py b/src/tagstudio/qt/mixed/tag_database.py index 81ea08289..f8df0c21f 100644 --- a/src/tagstudio/qt/mixed/tag_database.py +++ b/src/tagstudio/qt/mixed/tag_database.py @@ -59,15 +59,21 @@ def delete_tag(self, tag: Tag): return message_box = QMessageBox( - QMessageBox.Question, # pyright: ignore[reportAttributeAccessIssue] + QMessageBox.Icon.Question, Translations["tag.remove"], Translations.format("tag.confirm_delete", tag_name=self.lib.tag_display_name(tag)), - QMessageBox.Ok | QMessageBox.Cancel, # pyright: ignore[reportAttributeAccessIssue] ) + delete_button = message_box.addButton( + Translations["generic.delete_alt"], QMessageBox.ButtonRole.DestructiveRole + ) + cancel_button = message_box.addButton( + Translations["generic.cancel_alt"], QMessageBox.ButtonRole.RejectRole + ) + message_box.setEscapeButton(cancel_button) - result = message_box.exec() + message_box.exec() - if result != QMessageBox.Ok: # pyright: ignore[reportAttributeAccessIssue] + if message_box.clickedButton() != delete_button: return self.lib.remove_tag(tag.id) diff --git a/src/tagstudio/qt/mixed/tag_search.py b/src/tagstudio/qt/mixed/tag_search.py index e309d5d02..a9920fbc3 100644 --- a/src/tagstudio/qt/mixed/tag_search.py +++ b/src/tagstudio/qt/mixed/tag_search.py @@ -68,10 +68,14 @@ class TagSearchPanel(PanelWidget): is_tag_chooser: bool exclude: list[int] - _limit_items: list[int | str] = [25, 50, 100, 250, 500, Translations["tag.all_tags"]] - _default_limit_idx: int = 0 # 50 Tag Limit (Default) + _limit_items: list[int] = [25, 50, 100, 250, 500] + _default_limit_idx: int = 0 # 25 Tag Limit (Default) cur_limit_idx: int = _default_limit_idx - tag_limit: int | str = _limit_items[_default_limit_idx] + tag_limit: int = _limit_items[_default_limit_idx] + + @classmethod + def limit_item_labels(cls) -> list[str]: + return [str(item) for item in cls._limit_items] + [Translations["tag.all_tags"]] def __init__( self, @@ -102,7 +106,7 @@ def __init__( self.limit_combobox = QComboBox() self.limit_combobox.setEditable(False) - self.limit_combobox.addItems([str(x) for x in TagSearchPanel._limit_items]) + self.limit_combobox.addItems(TagSearchPanel.limit_item_labels()) self.limit_combobox.setCurrentIndex(TagSearchPanel._default_limit_idx) self.limit_combobox.currentIndexChanged.connect(self.update_limit) self.limit_layout.addWidget(self.limit_combobox) @@ -213,8 +217,8 @@ def update_tags(self, query: str | None = None): self.scroll_layout.takeAt(self.scroll_layout.count() - 1).widget().deleteLater() self.create_button_in_layout = False - # Only use the tag limit if it's an actual number (aka not "All Tags") - tag_limit = TagSearchPanel.tag_limit if isinstance(TagSearchPanel.tag_limit, int) else -1 + # -1 means "All Tags". + tag_limit = TagSearchPanel.tag_limit direct_tags, descendant_tags = self.lib.search_tags(name=query, limit=tag_limit) all_results = [t for t in direct_tags if t.id not in self.exclude] @@ -303,8 +307,8 @@ def update_limit(self, index: int): TagSearchPanel.cur_limit_idx = index - if index < len(self._limit_items) - 1: - TagSearchPanel.tag_limit = int(self._limit_items[index]) + if index < len(self._limit_items): + TagSearchPanel.tag_limit = self._limit_items[index] else: TagSearchPanel.tag_limit = -1 diff --git a/src/tagstudio/qt/ts_qt.py b/src/tagstudio/qt/ts_qt.py index edb3cafa5..2400700cf 100644 --- a/src/tagstudio/qt/ts_qt.py +++ b/src/tagstudio/qt/ts_qt.py @@ -491,7 +491,7 @@ def on_show_filenames_action(checked: bool): def on_decrease_thumbnail_size_action(): new_val = self.main_window.thumb_size_combobox.currentIndex() + 1 - if not (new_val + 1) > len(self.main_window.THUMB_SIZES): + if new_val < self.main_window.thumb_size_combobox.count(): self.main_window.thumb_size_combobox.setCurrentIndex(new_val) self.main_window.menu_bar.decrease_thumbnail_size_action.triggered.connect( @@ -993,9 +993,9 @@ def delete_file_confirmation(self, count: int, filename: Path | None = None) -> msg.setStyleSheet("font-weight:normal;") msg.setTextFormat(Qt.TextFormat.RichText) msg.setWindowTitle( - Translations["trash.title.singular"] + Translations["trash.dialog.title.singular"] if count == 1 - else Translations["trash.title.plural"] + else Translations["trash.dialog.title.plural"] ) msg.setIcon(QMessageBox.Icon.Warning) if count <= 1: @@ -1020,8 +1020,10 @@ def delete_file_confirmation(self, count: int, filename: Path | None = None) -> f"{perm_warning}
" ) - yes_button: QPushButton = msg.addButton("&Yes", QMessageBox.ButtonRole.YesRole) - msg.addButton("&No", QMessageBox.ButtonRole.NoRole) + yes_button: QPushButton = msg.addButton( + Translations["generic.yes"], QMessageBox.ButtonRole.YesRole + ) + msg.addButton(Translations["generic.no"], QMessageBox.ButtonRole.NoRole) msg.setDefaultButton(yes_button) return msg.exec() diff --git a/src/tagstudio/qt/views/main_window.py b/src/tagstudio/qt/views/main_window.py index 374df6465..9bb20a316 100644 --- a/src/tagstudio/qt/views/main_window.py +++ b/src/tagstudio/qt/views/main_window.py @@ -442,14 +442,18 @@ def rebuild_open_recent_library_menu( # View Component class MainWindow(QMainWindow): - THUMB_SIZES: list[tuple[str, int]] = [ - (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), + THUMB_SIZE_KEYS: list[tuple[str, int]] = [ + ("home.thumbnail_size.extra_large", 256), + ("home.thumbnail_size.large", 192), + ("home.thumbnail_size.medium", 128), + ("home.thumbnail_size.small", 96), + ("home.thumbnail_size.mini", 76), ] + @classmethod + def thumbnail_size_options(cls) -> list[tuple[str, int]]: + return [(Translations[key], size) for key, size in cls.THUMB_SIZE_KEYS] + def __init__(self, driver: "QtDriver", parent: QWidget | None = None) -> None: super().__init__(parent) self.rm = ResourceManager() @@ -668,8 +672,8 @@ def setup_extra_input_bar(self): self.thumb_size_combobox.setMinimumWidth(128) self.thumb_size_combobox.setMaximumWidth(352) self.extra_input_layout.addWidget(self.thumb_size_combobox) - for size in MainWindow.THUMB_SIZES: - self.thumb_size_combobox.addItem(size[0], size[1]) + for label, size in MainWindow.thumbnail_size_options(): + self.thumb_size_combobox.addItem(label, size) self.thumb_size_combobox.setCurrentIndex(2) # Default: Medium self.central_layout.addLayout(self.extra_input_layout, 5, 0, 1, 1) diff --git a/tests/qt/test_translated_ui_labels.py b/tests/qt/test_translated_ui_labels.py new file mode 100644 index 000000000..f9674bfe3 --- /dev/null +++ b/tests/qt/test_translated_ui_labels.py @@ -0,0 +1,95 @@ +# SPDX-FileCopyrightText: (c) TagStudio Contributors +# SPDX-License-Identifier: GPL-3.0-only + + +import os + +os.environ.setdefault("QT_QPA_PLATFORM", "offscreen") + +from pytestqt.qtbot import QtBot + +from tagstudio.core.enums import ShowFilepathOption, TagClickActionOption +from tagstudio.core.library.alchemy.library import Library +from tagstudio.qt.global_settings import Splash, Theme +from tagstudio.qt.mixed.settings_panel import SettingsPanel +from tagstudio.qt.mixed.tag_search import TagSearchPanel +from tagstudio.qt.translations import Translations +from tagstudio.qt.ts_qt import QtDriver +from tagstudio.qt.views.main_window import MainWindow + + +def _combobox_items(combobox) -> list[str]: + return [combobox.itemText(i) for i in range(combobox.count())] + + +def test_option_labels_use_current_translation(qtbot: QtBot, qt_driver: QtDriver): + original_language = Translations.current_language + try: + Translations.change_language("ja") + settings_panel = SettingsPanel(qt_driver) + qtbot.addWidget(settings_panel) + + assert _combobox_items(settings_panel.filepath_combobox) == [ + Translations["settings.filepath.option.full"], + Translations["settings.filepath.option.relative"], + Translations["settings.filepath.option.name"], + ] + assert settings_panel.filepath_combobox.itemData(0) == ShowFilepathOption.SHOW_FULL_PATHS + + assert _combobox_items(settings_panel.tag_click_action_combobox) == [ + Translations["settings.tag_click_action.open_edit"], + Translations["settings.tag_click_action.set_search"], + Translations["settings.tag_click_action.add_to_search"], + ] + assert ( + settings_panel.tag_click_action_combobox.itemData(0) + == TagClickActionOption.OPEN_EDIT + ) + + assert _combobox_items(settings_panel.theme_combobox) == [ + Translations["settings.theme.system"], + Translations["settings.theme.dark"], + Translations["settings.theme.light"], + ] + assert settings_panel.theme_combobox.itemData(0) == Theme.SYSTEM + + assert _combobox_items(settings_panel.splash_combobox) == [ + Translations["settings.splash.option.default"], + Translations["settings.splash.option.random"], + Translations["settings.splash.option.classic"], + Translations["settings.splash.option.goo_gears"], + Translations["settings.splash.option.ninety_five"], + ] + assert settings_panel.splash_combobox.itemData(0) == Splash.DEFAULT + finally: + Translations.change_language(original_language) + + +def test_search_limit_all_tags_uses_current_translation(qtbot: QtBot, library: Library): + original_language = Translations.current_language + try: + Translations.change_language("ja") + tag_search_panel = TagSearchPanel(library) + qtbot.addWidget(tag_search_panel) + + assert tag_search_panel.limit_combobox.itemText( + tag_search_panel.limit_combobox.count() - 1 + ) == Translations["tag.all_tags"] + finally: + Translations.change_language(original_language) + + +def test_thumbnail_size_labels_use_current_translation(): + original_language = Translations.current_language + try: + Translations.change_language("ja") + + assert MainWindow.thumbnail_size_options() == [ + (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), + ] + finally: + Translations.change_language(original_language)