diff --git a/tagstudio/resources/translations/en.json b/tagstudio/resources/translations/en.json index 0962a594..4fe852de 100644 --- a/tagstudio/resources/translations/en.json +++ b/tagstudio/resources/translations/en.json @@ -199,6 +199,7 @@ "menu.macros.folders_to_tags": "Folders to Tags", "menu.macros": "&Macros", "menu.select": "Select", + "menu.settings": "Settings...", "menu.tools.fix_duplicate_files": "Fix Duplicate &Files", "menu.tools.fix_unlinked_entries": "Fix &Unlinked Entries", "menu.tools": "&Tools", @@ -209,6 +210,7 @@ "namespace.create.title": "Create Namespace", "namespace.new.button": "New Namespace", "namespace.new.prompt": "Create a New Namespace to Start Adding Custom Colors!", + "preview.multiple_selection": "{count} Items Selected", "preview.no_selection": "No Items Selected", "select.add_tag_to_selected": "Add Tag to Selected", "select.all": "Select All", @@ -216,9 +218,12 @@ "edit.copy_fields": "Copy Fields", "edit.paste_fields": "Paste Fields", "settings.clear_thumb_cache.title": "Clear Thumbnail Cache", + "settings.language": "Language", "settings.open_library_on_start": "Open Library on Start", + "settings.restart_required": "Please restart TagStudio for changes to take effect.", "settings.show_filenames_in_grid": "Show Filenames in Grid", "settings.show_recent_libraries": "Show Recent Libraries", + "settings.title": "Settings", "sorting.direction.ascending": "Ascending", "sorting.direction.descending": "Descending", "splash.opening_library": "Opening Library \"{library_path}\"...", diff --git a/tagstudio/src/core/enums.py b/tagstudio/src/core/enums.py index ea68838b..84debc91 100644 --- a/tagstudio/src/core/enums.py +++ b/tagstudio/src/core/enums.py @@ -17,6 +17,7 @@ class SettingItems(str, enum.Enum): SHOW_FILENAMES = "show_filenames" AUTOPLAY = "autoplay_videos" THUMB_CACHE_SIZE_LIMIT = "thumb_cache_size_limit" + LANGUAGE = "language" class Theme(str, enum.Enum): diff --git a/tagstudio/src/qt/modals/settings_panel.py b/tagstudio/src/qt/modals/settings_panel.py new file mode 100644 index 00000000..a499f6dd --- /dev/null +++ b/tagstudio/src/qt/modals/settings_panel.py @@ -0,0 +1,73 @@ +# Copyright (C) 2025 Travis Abendshien (CyanVoxel). +# Licensed under the GPL-3.0 License. +# Created for TagStudio: https://github.com/CyanVoxel/TagStudio + + +from PySide6.QtCore import Qt +from PySide6.QtWidgets import QComboBox, QFormLayout, QLabel, QVBoxLayout, QWidget +from src.core.enums import SettingItems +from src.qt.translations import Translations +from src.qt.widgets.panel import PanelWidget + + +class SettingsPanel(PanelWidget): + def __init__(self, driver): + super().__init__() + self.driver = driver + self.setMinimumSize(320, 200) + self.root_layout = QVBoxLayout(self) + self.root_layout.setContentsMargins(6, 0, 6, 0) + + self.form_container = QWidget() + self.form_layout = QFormLayout(self.form_container) + self.form_layout.setContentsMargins(0, 0, 0, 0) + + self.restart_label = QLabel() + self.restart_label.setHidden(True) + Translations.translate_qobject(self.restart_label, "settings.restart_required") + self.restart_label.setAlignment(Qt.AlignmentFlag.AlignCenter) + + language_label = QLabel() + Translations.translate_qobject(language_label, "settings.language") + self.languages = { + # "Cantonese (Traditional)": "yue_Hant", # Empty + "Chinese (Traditional)": "zh_Hant", + # "Czech": "cs", # Minimal + # "Danish": "da", # Minimal + "Dutch": "nl", + "English": "en", + "Filipino": "fil", + "French": "fr", + "German": "de", + "Hungarian": "hu", + # "Italian": "it", # Minimal + "Norwegian Bokmål": "nb_NO", + "Polish": "pl", + "Portuguese (Brazil)": "pt_BR", + # "Portuguese (Portugal)": "pt", # Empty + "Russian": "ru", + "Spanish": "es", + "Swedish": "sv", + "Tamil": "ta", + "Toki Pona": "tok", + "Turkish": "tr", + } + self.language_combobox = QComboBox() + self.language_combobox.addItems(list(self.languages.keys())) + current_lang: str = str( + driver.settings.value(SettingItems.LANGUAGE, defaultValue="en", type=str) + ) + current_lang = "en" if current_lang not in self.languages.values() else current_lang + self.language_combobox.setCurrentIndex(list(self.languages.values()).index(current_lang)) + self.language_combobox.currentIndexChanged.connect( + lambda: self.restart_label.setHidden(False) + ) + self.form_layout.addRow(language_label, self.language_combobox) + + self.root_layout.addWidget(self.form_container) + self.root_layout.addStretch(1) + self.root_layout.addWidget(self.restart_label) + + def get_language(self) -> str: + values: list[str] = list(self.languages.values()) + return values[self.language_combobox.currentIndex()] diff --git a/tagstudio/src/qt/translations.py b/tagstudio/src/qt/translations.py index 5d2dcea0..fae788ac 100644 --- a/tagstudio/src/qt/translations.py +++ b/tagstudio/src/qt/translations.py @@ -70,16 +70,20 @@ class Translator: Also formats the translation with the given keyword arguments. """ - if key in self._strings: - self._strings[key].changed.connect(lambda text: setter(self.__format(text, **kwargs))) + # TODO: Fix so deleted Qt objects aren't referenced any longer + # if key in self._strings: + # self._strings[key].changed.connect(lambda text: setter(self.__format(text, **kwargs))) setter(self.translate_formatted(key, **kwargs)) def __format(self, text: str, **kwargs) -> str: try: return text.format(**kwargs) - except KeyError: - logger.warning( - "Error while formatting translation.", text=text, kwargs=kwargs, language=self._lang + except (KeyError, ValueError): + logger.error( + "[Translations] Error while formatting translation.", + text=text, + kwargs=kwargs, + language=self._lang, ) return text @@ -87,9 +91,7 @@ class Translator: return self.__format(self[key], **kwargs) def __getitem__(self, key: str) -> str: - # return "???" - return self._strings[key].value if key in self._strings else "Not Translated" + return self._strings[key].value if key in self._strings else f"[{key}]" Translations = Translator() -# Translations.change_language("de") diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index 8e64c27d..f02cf8ed 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -87,6 +87,7 @@ from src.qt.modals.file_extension import FileExtensionModal from src.qt.modals.fix_dupes import FixDupeFilesModal from src.qt.modals.fix_unlinked import FixUnlinkedEntriesModal from src.qt.modals.folders_to_tags import FoldersToTagsModal +from src.qt.modals.settings_panel import SettingsPanel from src.qt.modals.tag_color_manager import TagColorManager from src.qt.modals.tag_database import TagDatabasePanel from src.qt.modals.tag_search import TagSearchPanel @@ -197,6 +198,10 @@ class QtDriver(DriverMixin, QObject): ) self.config_path = self.settings.fileName() + Translations.change_language( + str(self.settings.value(SettingItems.LANGUAGE, defaultValue="en", type=str)) + ) + # NOTE: This should be a per-library setting rather than an application setting. thumb_cache_size_limit: int = int( str( @@ -366,19 +371,6 @@ class QtDriver(DriverMixin, QObject): file_menu.addMenu(self.open_recent_library_menu) self.update_recent_lib_menu() - open_on_start_action = QAction(self) - Translations.translate_qobject(open_on_start_action, "settings.open_library_on_start") - open_on_start_action.setCheckable(True) - open_on_start_action.setChecked( - bool(self.settings.value(SettingItems.START_LOAD_LAST, defaultValue=True, type=bool)) - ) - open_on_start_action.triggered.connect( - lambda checked: self.settings.setValue(SettingItems.START_LOAD_LAST, checked) - ) - file_menu.addAction(open_on_start_action) - - file_menu.addSeparator() - self.save_library_backup_action = QAction(menu_bar) Translations.translate_qobject(self.save_library_backup_action, "menu.file.save_backup") self.save_library_backup_action.triggered.connect( @@ -397,6 +389,23 @@ class QtDriver(DriverMixin, QObject): self.save_library_backup_action.setEnabled(False) file_menu.addAction(self.save_library_backup_action) + file_menu.addSeparator() + settings_action = QAction(self) + Translations.translate_qobject(settings_action, "menu.settings") + settings_action.triggered.connect(self.open_settings_modal) + file_menu.addAction(settings_action) + + open_on_start_action = QAction(self) + Translations.translate_qobject(open_on_start_action, "settings.open_library_on_start") + open_on_start_action.setCheckable(True) + open_on_start_action.setChecked( + bool(self.settings.value(SettingItems.START_LOAD_LAST, defaultValue=True, type=bool)) + ) + open_on_start_action.triggered.connect( + lambda checked: self.settings.setValue(SettingItems.START_LOAD_LAST, checked) + ) + file_menu.addAction(open_on_start_action) + file_menu.addSeparator() self.refresh_dir_action = QAction(menu_bar) @@ -1830,6 +1839,24 @@ class QtDriver(DriverMixin, QObject): self.settings.sync() self.update_recent_lib_menu() + def open_settings_modal(self): + # TODO: Implement a proper settings panel, and don't re-create it each time it's opened. + settings_panel = SettingsPanel(self) + modal = PanelModal( + widget=settings_panel, + done_callback=lambda: self.update_language_settings(settings_panel.get_language()), + has_save=False, + ) + Translations.translate_with_setter(modal.setTitle, "settings.title") + Translations.translate_with_setter(modal.setWindowTitle, "settings.title") + modal.show() + + def update_language_settings(self, language: str): + Translations.change_language(language) + + self.settings.setValue(SettingItems.LANGUAGE, language) + self.settings.sync() + def open_library(self, path: Path) -> None: """Open a TagStudio library.""" translation_params = {"key": "splash.opening_library", "library_path": str(path)} diff --git a/tagstudio/src/qt/widgets/preview/file_attributes.py b/tagstudio/src/qt/widgets/preview/file_attributes.py index fa1a6c7e..50f3205f 100644 --- a/tagstudio/src/qt/widgets/preview/file_attributes.py +++ b/tagstudio/src/qt/widgets/preview/file_attributes.py @@ -23,6 +23,7 @@ from src.core.enums import Theme from src.core.library.alchemy.library import Library from src.core.media_types import MediaCategories from src.qt.helpers.file_opener import FileOpenerHelper, FileOpenerLabel +from src.qt.translations import Translations if typing.TYPE_CHECKING: from src.qt.ts_qt import QtDriver @@ -108,16 +109,22 @@ class FileAttributes(QWidget): created = dt.fromtimestamp(filepath.stat().st_ctime) modified: dt = dt.fromtimestamp(filepath.stat().st_mtime) self.date_created_label.setText( - f"Date Created: {dt.strftime(created, "%a, %x, %X")}" # TODO: Translate + f"{Translations["file.date_created"]}: " + f"{dt.strftime(created, "%a, %x, %X")}" ) self.date_modified_label.setText( - f"Date Modified: {dt.strftime(modified, "%a, %x, %X")}" # TODO: Translate + f"{Translations["file.date_modified"]}: " + f"{dt.strftime(modified, "%a, %x, %X")}" ) self.date_created_label.setHidden(False) self.date_modified_label.setHidden(False) elif filepath: - self.date_created_label.setText("Date Created: N/A") # TODO: Translate - self.date_modified_label.setText("Date Modified: N/A") # TODO: Translate + self.date_created_label.setText( + f"{Translations["file.date_created"]}: N/A" + ) + self.date_modified_label.setText( + f"{Translations["file.date_modified"]}: N/A" + ) self.date_created_label.setHidden(False) self.date_modified_label.setHidden(False) else: @@ -132,7 +139,7 @@ class FileAttributes(QWidget): if not filepath: self.layout().setSpacing(0) self.file_label.setAlignment(Qt.AlignmentFlag.AlignCenter) - self.file_label.setText("No Items Selected") # TODO: Translate + self.file_label.setText(f"{Translations["preview.no_selection"]}") self.file_label.set_file_path("") self.file_label.setCursor(Qt.CursorShape.ArrowCursor) self.dimensions_label.setText("") @@ -221,7 +228,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(f"{count} Items Selected") # TODO: Translate + Translations.translate_qobject(self.file_label, "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_panel.py b/tagstudio/src/qt/widgets/preview_panel.py index ab1791a9..9df1222d 100644 --- a/tagstudio/src/qt/widgets/preview_panel.py +++ b/tagstudio/src/qt/widgets/preview_panel.py @@ -105,14 +105,14 @@ class PreviewPanel(QWidget): self.add_tag_button.setCursor(Qt.CursorShape.PointingHandCursor) self.add_tag_button.setMinimumHeight(28) self.add_tag_button.setStyleSheet(PreviewPanel.button_style) - self.add_tag_button.setText("Add Tag") # TODO: Translate + Translations.translate_qobject(self.add_tag_button, "tag.add") self.add_field_button = QPushButton() self.add_field_button.setEnabled(False) self.add_field_button.setCursor(Qt.CursorShape.PointingHandCursor) self.add_field_button.setMinimumHeight(28) self.add_field_button.setStyleSheet(PreviewPanel.button_style) - self.add_field_button.setText("Add Field") # TODO: Translate + Translations.translate_qobject(self.add_field_button, "library.field.add") add_buttons_layout.addWidget(self.add_tag_button) add_buttons_layout.addWidget(self.add_field_button)