mirror of
https://github.com/TagStudioDev/TagStudio.git
synced 2026-02-01 23:59:10 +00:00
Merge branch 'main' into keyboard_nav_improvements
This commit is contained in:
@@ -1,3 +1,7 @@
|
||||
# Date: Fri, 3 May 2024 19:40:36 -0700
|
||||
# Reformatted using Ruff
|
||||
089c8dd50cbf22e75721ecd2c0e0acfd3deca7f3
|
||||
|
||||
# Date: Fri, 13 Sep 2024 00:28:00 -0700
|
||||
# ci(ruff)!: update ruff linter config, refactor to comply
|
||||
b6e216760557c5507b12f210e1e48c531f49ffa3
|
||||
|
||||
@@ -49,7 +49,7 @@ TagStudio is a photo & file organization application with an underlying tag-base
|
||||
- To provide powerful methods for organization, notably the concept of tag inheritance, or “taggable tags” _(and in the near future, the combination of composition-based tags)._
|
||||
- To create an implementation of such a system that is resilient against a user’s actions outside the program (modifying, moving, or renaming files) while also not burdening the user with mandatory sidecar files or requiring them to change their existing file structures and workflows.
|
||||
- To support a wide range of users spanning across different platforms, multi-user setups, and those with large (several terabyte) libraries.
|
||||
- To make the darn thing look like nice, too. It’s 2024, not 1994.
|
||||
- To make the darn thing look like nice, too. It’s 2025, not 1995.
|
||||
|
||||
## Priorities
|
||||
|
||||
|
||||
@@ -61,7 +61,6 @@
|
||||
"library.field.remove": "Feld entfernen",
|
||||
"library.missing": "Dateiort fehlt",
|
||||
"library.name": "Bibliothek",
|
||||
"library.refresh.scanning": "Durchsuche Verzeichnisse nach neuen Dateien...\n%{x + 1} Datei%{„en“ if x + 1 != 1 else „“} durchsucht, %{len(self.lib.files_not_in_library)} Neue Datei%{„en“ if len(self.lib.files_not_in_library) != 1 else „“} gefunden",
|
||||
"library.refresh.scanning_preparing": "Überprüfe Verzeichnisse auf neue Dateien...\nBereite vor...",
|
||||
"library.refresh.title": "Verzeichnisse werden aktualisiert",
|
||||
"library.scan_library.title": "Bibliothek wird scannen",
|
||||
|
||||
@@ -1,27 +1,40 @@
|
||||
{
|
||||
"app.git": "Git Commit",
|
||||
"app.pre_release": "Pre-Release",
|
||||
"app.title": "{base_title} - Library '{library_dir}'",
|
||||
"drop_import.description": "The following files have filenames already exist in the library",
|
||||
"drop_import.duplicates_choice.plural": "The following {count} files have filenames that already exist in the library.",
|
||||
"drop_import.duplicates_choice.singular": "The following file has a filename that already exists in the library.",
|
||||
"drop_import.progress.label.initial": "Importing New Files...",
|
||||
"drop_import.progress.label.plural": "Importing New Files...\n{count} Files Imported.{suffix}",
|
||||
"drop_import.progress.label.singular": "Importing New Files...\n1 File imported.{suffix}",
|
||||
"drop_import.progress.window_title": "Import Files",
|
||||
"drop_import.title": "Conflicting File(s)",
|
||||
"edit.tag_manager": "Manage Tags",
|
||||
"entries.duplicate.merge.label": "Merging Duplicate Entries",
|
||||
"entries.duplicate.merge.label": "Merging Duplicate Entries...",
|
||||
"entries.duplicate.merge": "Merge Duplicate Entries",
|
||||
"entries.duplicate.refresh": "Refresh Duplicate Entries",
|
||||
"entries.duplicates.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.",
|
||||
"entries.mirror.confirmation": "Are you sure you want to mirror the following %{len(self.lib.dupe_files)} Entries?",
|
||||
"entries.mirror.label": "Mirroring 1/%{count} Entries...",
|
||||
"entries.mirror.confirmation": "Are you sure you want to mirror the following {count} Entries?",
|
||||
"entries.mirror.label": "Mirroring {idx}/{total} Entries...",
|
||||
"entries.mirror.title": "Mirroring Entries",
|
||||
"entries.mirror": "Mirror",
|
||||
"entries.mirror.window_title": "Mirror Entries",
|
||||
"entries.mirror": "&Mirror",
|
||||
"entries.tags": "Tags",
|
||||
"entries.unlinked.delete.confirm": "Are you sure you want to delete the following %{len(self.lib.missing_files)} entries?",
|
||||
"entries.unlinked.delete.deleting_count": "Deleting %{x[0]+1}/{len(self.lib.missing_files)} Unlinked Entries",
|
||||
"entries.unlinked.delete_alt": "De&lete Unlinked Entries",
|
||||
"entries.unlinked.delete.confirm": "Are you sure you want to delete the following {count} entries?",
|
||||
"entries.unlinked.delete.deleting_count": "Deleting {idx}/{count} Unlinked Entries",
|
||||
"entries.unlinked.delete.deleting": "Deleting Entries",
|
||||
"entries.unlinked.delete": "Delete Unlinked Entries",
|
||||
"entries.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 or deleted if desired.",
|
||||
"entries.unlinked.refresh_all": "Refresh All",
|
||||
"entries.unlinked.relink.attempting": "Attempting to Relink %{x[0]+1}/%{len(self.lib.missing_files)} Entries, %{self.fixed} Successfully Relinked",
|
||||
"entries.unlinked.relink.manual": "Manual Relink",
|
||||
"entries.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.<br><br>Unlinked entries may be automatically relinked via searching your directories or deleted if desired.",
|
||||
"entries.unlinked.missing_count.none": "Unlinked Entries: N/A",
|
||||
"entries.unlinked.missing_count.some": "Unlinked Entries: {count}",
|
||||
"entries.unlinked.refresh_all": "&Refresh All",
|
||||
"entries.unlinked.relink.attempting": "Attempting to Relink {idx}/{missing_count} Entries, {fixed_count} Successfully Relinked",
|
||||
"entries.unlinked.relink.manual": "&Manual Relink",
|
||||
"entries.unlinked.relink.title": "Relinking Entries",
|
||||
"entries.unlinked.scanning": "Scanning Library for Unlinked Entries...",
|
||||
"entries.unlinked.search_and_relink": "Search && Relink",
|
||||
"entries.unlinked.search_and_relink": "&Search && Relink",
|
||||
"entries.unlinked.title": "Fix Unlinked Entries",
|
||||
"field.copy": "Copy Field",
|
||||
"field.edit": "Edit Field",
|
||||
@@ -30,21 +43,22 @@
|
||||
"file.date_created": "Date Created",
|
||||
"file.date_modified": "Date Modified",
|
||||
"file.dimensions": "Dimensions",
|
||||
"file.duplicates.description": "TagStudio supports importing DupeGuru results to manage duplicate files.",
|
||||
"file.duplicates.dupeguru.advice": "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.",
|
||||
"file.duplicates.dupeguru.file_extension": "DupeGuru Files (*.dupeguru)",
|
||||
"file.duplicates.dupeguru.load_file": "Load DupeGuru File",
|
||||
"file.duplicates.dupeguru.load_file": "&Load DupeGuru File",
|
||||
"file.duplicates.dupeguru.no_file": "No DupeGuru File Selected",
|
||||
"file.duplicates.dupeguru.open_file": "Open DupeGuru Results File",
|
||||
"file.duplicates.fix": "Fix Duplicate Files",
|
||||
"file.duplicates.matches_uninitialized": "Duplicate File Matches: N/A",
|
||||
"file.duplicates.matches": "Duplicate File Matches: %{count}",
|
||||
"file.duplicates.mirror_entries": "Mirror Entries",
|
||||
"file.duplicates.matches": "Duplicate File Matches: {count}",
|
||||
"file.duplicates.mirror_entries": "&Mirror Entries",
|
||||
"file.duplicates.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.",
|
||||
"file.duration": "Length",
|
||||
"file.not_found": "File Not Found",
|
||||
"file.open_file_with": "Open file with",
|
||||
"file.open_file": "Open file",
|
||||
"file.open_location.generic": "Show file in explorer",
|
||||
"file.open_location.generic": "Show file in file explorer",
|
||||
"file.open_location.mac": "Reveal in Finder",
|
||||
"file.open_location.windows": "Show in File Explorer",
|
||||
"folders_to_tags.close_all": "Close All",
|
||||
@@ -53,52 +67,110 @@
|
||||
"folders_to_tags.open_all": "Open All",
|
||||
"folders_to_tags.title": "Create Tags From Folders",
|
||||
"generic.add": "Add",
|
||||
"generic.apply_alt": "&Apply",
|
||||
"generic.apply": "Apply",
|
||||
"generic.cancel_alt": "&Cancel",
|
||||
"generic.cancel": "Cancel",
|
||||
"generic.close": "Close",
|
||||
"generic.continue": "Continue",
|
||||
"generic.copy": "Copy",
|
||||
"generic.cut": "Cut",
|
||||
"generic.delete_alt": "&Delete",
|
||||
"generic.delete": "Delete",
|
||||
"generic.done_alt": "&Done",
|
||||
"generic.done": "Done",
|
||||
"generic.edit_alt": "&Edit",
|
||||
"generic.edit": "Edit",
|
||||
"generic.filename": "Filename",
|
||||
"generic.navigation.back": "Back",
|
||||
"generic.navigation.next": "Next",
|
||||
"generic.overwrite_alt": "&Overwrite",
|
||||
"generic.overwrite": "Overwrite",
|
||||
"generic.paste": "Paste",
|
||||
"generic.recent_libraries": "Recent Libraries",
|
||||
"generic.rename_alt": "&Rename",
|
||||
"generic.rename": "Rename",
|
||||
"generic.save": "Save",
|
||||
"generic.skip_alt": "&Skip",
|
||||
"generic.skip": "Skip",
|
||||
"help.visit_github": "Visit GitHub Repository",
|
||||
"home.search_entries": "Search Entries",
|
||||
"home.search_library": "Search Library",
|
||||
"home.search_tags": "Search Tags",
|
||||
"home.search": "Search",
|
||||
"home.thumbnail_size.extra_large": "Extra Large Thumbnails",
|
||||
"home.thumbnail_size.large": "Large Thumbnails",
|
||||
"home.thumbnail_size.medium": "Medium Thumbnails",
|
||||
"home.thumbnail_size.mini": "Mini Thumbnails",
|
||||
"home.thumbnail_size.small": "Small Thumbnails",
|
||||
"home.thumbnail_size": "Thumbnail Size",
|
||||
"ignore_list.add_extension": "Add Extension",
|
||||
"ignore_list.add_extension": "&Add Extension",
|
||||
"ignore_list.mode.exclude": "Exclude",
|
||||
"ignore_list.mode.include": "Include",
|
||||
"ignore_list.mode.label": "List Mode",
|
||||
"ignore_list.mode.label": "List Mode:",
|
||||
"ignore_list.title": "File Extensions",
|
||||
"json_migration.checking_for_parity": "Checking for Parity...",
|
||||
"json_migration.creating_database_tables": "Creating SQL Database Tables...",
|
||||
"json_migration.description": "<br>Start and preview the results of the library migration process. The converted library will <i>not</i> be used unless you click \"Finish Migration\". <br><br>Library data should either have matching values or feature a \"Matched\" label. Values that do not match will be displayed in red and feature a \"<b>(!)</b>\" symbol next to them.<br><center><i>This process may take up to several minutes for larger libraries.</i></center>",
|
||||
"json_migration.discrepancies_found.description": "Discrepancies were found between the original and converted library formats. Please review and choose to whether continue with the migration or to cancel.",
|
||||
"json_migration.discrepancies_found": "Library Discrepancies Found",
|
||||
"json_migration.finish_migration": "Finish Migration",
|
||||
"json_migration.heading.aliases": "Aliases:",
|
||||
"json_migration.heading.colors": "Colors:",
|
||||
"json_migration.heading.differ": "Discrepancy",
|
||||
"json_migration.heading.entires": "Entries:",
|
||||
"json_migration.heading.extension_list_type": "Extension List Type:",
|
||||
"json_migration.heading.fields": "Fields:",
|
||||
"json_migration.heading.file_extension_list": "File Extension List:",
|
||||
"json_migration.heading.match": "Matched",
|
||||
"json_migration.heading.parent_tags": "Parent Tags:",
|
||||
"json_migration.heading.paths": "Paths:",
|
||||
"json_migration.heading.shorthands": "Shorthands:",
|
||||
"json_migration.heading.tags": "Tags:",
|
||||
"json_migration.info.description": "Library save files created with TagStudio versions <b>9.4 and below</b> will need to be migrated to the new <b>v9.5+</b> format.<br><h2>What you need to know:</h2><ul><li>Your existing library save file will <b><i>NOT</i></b> be deleted</li><li>Your personal files will <b><i>NOT</i></b> be deleted, moved, or modified</li><li>The new v9.5+ save format can not be opened in earlier versions of TagStudio</li></ul>",
|
||||
"json_migration.migrating_files_entries": "Migrating {entries:,d} File Entries...",
|
||||
"json_migration.migration_complete_with_discrepancies": "Migration Complete, Discrepancies Found",
|
||||
"json_migration.migration_complete": "Migration Complete!",
|
||||
"json_migration.start_and_preview": "Start and Preview",
|
||||
"json_migration.title.new_lib": "<h2>v9.5+ Library</h2>",
|
||||
"json_migration.title.old_lib": "<h2>v9.4 Library</h2>",
|
||||
"json_migration.title": "Save Format Migration: \"{path}\"",
|
||||
"landing.open_create_library": "Open/Create Library {shortcut}",
|
||||
"library.field.add": "Add Field",
|
||||
"library.field.confirm_remove": "Are you sure you want to remove this \"%{self.lib.get_field_attr(field, \"name\")}\" field?",
|
||||
"library.field.confirm_remove": "Are you sure you want to remove this \"{name}\" field?",
|
||||
"library.field.mixed_data": "Mixed Data",
|
||||
"library.field.remove": "Remove Field",
|
||||
"library.missing": "Library Location is Missing",
|
||||
"library.name": "Library",
|
||||
"library.refresh.scanning_preparing": "Scanning Directories for New Files...\nPreparing...",
|
||||
"library.refresh.scanning": "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",
|
||||
"library.refresh.scanning.plural": "Scanning Directories for New Files...\n{searched_count} Files Searched, {found_count} New Files Found",
|
||||
"library.refresh.scanning.singular": "Scanning Directories for New Files...\n{searched_count} File Searched, {found_count} New Files Found",
|
||||
"library.refresh.title": "Refreshing Directories",
|
||||
"library.scan_library.title": "Scanning Library",
|
||||
"macros.running.dialog.new_entries": "Running Configured Macros on %{x + 1}/%{len(new_ids)} New Entries",
|
||||
"macros.running.dialog.new_entries": "Running Configured Macros on {count}/{total} New Entries",
|
||||
"macros.running.dialog.title": "Running Macros on New Entries",
|
||||
"media_player.autoplay": "Autoplay",
|
||||
"menu.edit.ignore_list": "Ignore Files and Folders",
|
||||
"menu.edit.manage_file_extensions": "Manage File Extensions",
|
||||
"menu.edit.manage_tags": "Manage Tags",
|
||||
"menu.edit.new_tag": "New &Tag",
|
||||
"menu.edit": "Edit",
|
||||
"menu.file.close_library": "&Close Library",
|
||||
"menu.file.new_library": "New Library",
|
||||
"menu.file.open_create_library": "Open/Create Library",
|
||||
"menu.file.open_create_library": "&Open/Create Library",
|
||||
"menu.file.open_library": "Open Library",
|
||||
"menu.file.refresh_directories": "&Refresh Directories",
|
||||
"menu.file.save_backup": "&Save Library Backup",
|
||||
"menu.file.save_library": "Save Library",
|
||||
"menu.file": "File",
|
||||
"menu.help": "Help",
|
||||
"menu.macros": "Macros",
|
||||
"menu.file": "&File",
|
||||
"menu.help": "&Help",
|
||||
"menu.macros.folders_to_tags": "Folders to Tags",
|
||||
"menu.macros": "&Macros",
|
||||
"menu.select": "Select",
|
||||
"menu.tools": "Tools",
|
||||
"menu.view": "View",
|
||||
"menu.tools.fix_duplicate_files": "Fix Duplicate &Files",
|
||||
"menu.tools.fix_unlinked_entries": "Fix &Unlinked Entries",
|
||||
"menu.tools": "&Tools",
|
||||
"menu.view": "&View",
|
||||
"menu.window": "Window",
|
||||
"preview.no_selection": "No Items Selected",
|
||||
"select.all": "Select All",
|
||||
@@ -106,27 +178,39 @@
|
||||
"settings.open_library_on_start": "Open Library on Start",
|
||||
"settings.show_filenames_in_grid": "Show Filenames in Grid",
|
||||
"settings.show_recent_libraries": "Show Recent Libraries",
|
||||
"splash.opening_library": "Opening Library",
|
||||
"status.library_backup_success": "Library Backup Saved at:",
|
||||
"splash.opening_library": "Opening Library \"{library_path}\"...",
|
||||
"status.library_backup_in_progress": "Saving Library Backup...",
|
||||
"status.library_backup_success": "Library Backup Saved at: \"{path}\" ({time_span})",
|
||||
"status.library_closed": "Library Closed ({time_span})",
|
||||
"status.library_closing": "Closing Library...",
|
||||
"status.library_save_success": "Library Saved and Closed!",
|
||||
"status.library_search_query": "Searching Library for",
|
||||
"status.results_found": "{results.total_count} Results Found",
|
||||
"status.library_search_query": "Searching Library...",
|
||||
"status.results_found": "{count} Results Found ({time_span})",
|
||||
"status.results": "Results",
|
||||
"tag_manager.title": "Library Tags",
|
||||
"tag.add_to_search": "Add to Search",
|
||||
"tag.add.plural": "Add Tags",
|
||||
"tag.add": "Add Tag",
|
||||
"tag.aliases": "Aliases",
|
||||
"tag.color": "Color",
|
||||
"tag.confirm_delete": "Are you sure you want to delete the tag \"{tag_name}\"?",
|
||||
"tag.create": "Create Tag",
|
||||
"tag.edit": "Edit Tag",
|
||||
"tag.name": "Name",
|
||||
"tag.new": "New Tag",
|
||||
"tag.parent_tags.add": "Add Parent Tag(s)",
|
||||
"tag.parent_tags.description": "This tag can be treated as a substitute for any of these Parent Tags in searches.",
|
||||
"tag.parent_tags": "Parent Tags",
|
||||
"tag.remove": "Remove Tag",
|
||||
"tag.search_for_tag": "Search for Tag",
|
||||
"tag.shorthand": "Shorthand",
|
||||
"tag.tag_name_required": "Tag Name (Required)",
|
||||
"view.size.0": "Mini",
|
||||
"view.size.1": "Small",
|
||||
"view.size.2": "Medium",
|
||||
"view.size.3": "Large",
|
||||
"view.size.4": "Extra Large"
|
||||
"view.size.4": "Extra Large",
|
||||
"window.message.error_opening_library": "Error opening library.",
|
||||
"window.title.error": "Error",
|
||||
"window.title.open_create_library": "Open/Create Library"
|
||||
}
|
||||
|
||||
@@ -61,7 +61,6 @@
|
||||
"library.field.remove": "Eliminar campo",
|
||||
"library.missing": "Falta la ubicación",
|
||||
"library.name": "Biblioteca",
|
||||
"library.refresh.scanning": "Buscando archivos nuevos en los directorios...\n%{x + 1} Archivo%{\"s\" if x + 1 != 1 else \"\"} Buscado, %{len(self.lib.files_not_in_library)} Archivos nuevos encontrados",
|
||||
"library.refresh.scanning_preparing": "Buscar archivos nuevos en los directorios...\nPreparando...",
|
||||
"library.refresh.title": "Refrescar directorios",
|
||||
"library.scan_library.title": "Escaneando la biblioteca",
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
"library.field.add": "Magdagdag ng Field",
|
||||
"library.field.remove": "Tanggalin ang Field",
|
||||
"library.name": "Library",
|
||||
"library.refresh.scanning": "Sina-scan ang Mga Direktoryo para sa Mga Bagong File...\n%{x + 1} %{\"mga\" if x + 1 != 1 else \"\"} File na nahanap, %{len(self.lib.files_not_in_library)} Mga Bagong File na Nahanap",
|
||||
"library.refresh.scanning_preparing": "Sina-scan ang Mga Direktoryo para sa Mga Bagong File...\nNaghahanda...",
|
||||
"library.refresh.title": "Nire-refresh ang Mga Direktoryo",
|
||||
"macros.running.dialog.new_entries": "Tinatakbo ang Mga Naka-configure na Macro sa %{x + 1}/%{len(new_ids)} Mga Bagong Entry",
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
{
|
||||
"app.git": "Git Commit",
|
||||
"app.pre_release": "Version Préliminaire",
|
||||
"edit.tag_manager": "Gérer les Tags",
|
||||
"entries.duplicate.merge": "Fusion des Duplicatas",
|
||||
"entries.duplicate.merge.label": "Fusionner les duplicatas",
|
||||
"entries.duplicate.refresh": "Rafraichir les Entrées en Doublon",
|
||||
"entries.duplicates.description": "Les entrées dupliquées sont définies comme des entrées multiple qui pointent vers le même fichier sur le disque. Les fusionner va combiner les labels et metadatas de tous les duplicatas vers une seule entrée consolidée. Elles ne doivent pas être confondues avec les \"fichiers en doublon\", qui sont des doublons de vos fichiers en dehors de TagStudio.",
|
||||
@@ -19,6 +23,10 @@
|
||||
"entries.unlinked.scanning": "Balayage de la Bibliothèque pour trouver des Entrées non Liées...",
|
||||
"entries.unlinked.search_and_relink": "Rechercher && Relier",
|
||||
"entries.unlinked.title": "Réparation des Entrées non Liées",
|
||||
"field.copy": "Copier le Champ",
|
||||
"field.edit": "Modifier le Champ",
|
||||
"field.paste": "Coller le Champ",
|
||||
"file.date_added": "Date Ajoutée",
|
||||
"file.date_created": "Date de Création",
|
||||
"file.date_modified": "Date de Modification",
|
||||
"file.dimensions": "Dimensions",
|
||||
@@ -32,9 +40,14 @@
|
||||
"file.duplicates.matches_uninitialized": "Dupliquer les Correspondances de Fichier : N/A",
|
||||
"file.duplicates.mirror.description": "Repliquer les données d'entrée dans chaque jeu de correspondances en double, en combinant toutes les données sans supprimer ni dupliquer de champs. Cette opération ne supprime aucun fichier ni aucune donnée.",
|
||||
"file.duplicates.mirror_entries": "Répliquer les Entrées",
|
||||
"file.not_found": "Fichier non trouvé:",
|
||||
"file.duration": "Durée",
|
||||
"file.not_found": "Fichier non trouvé",
|
||||
"file.open_file": "Ouvrir un Fichier",
|
||||
"file.open_file_with": "Ouvrir le fichier avec",
|
||||
"file.open_location.generic": "Ouvrir un Fichier dans l'Explorateur",
|
||||
"file.open_location.mac": "Montrer dans le Finder",
|
||||
"file.open_location.windows": "Montrer dans l'explorateur de Fichiers",
|
||||
"folders_to_tags.close_all": "Tout Fermer",
|
||||
"folders_to_tags.converting": "Conversion des dossiers en Labels",
|
||||
"folders_to_tags.description": "Créé des labels basés sur votre arborescence de dossier et les applique à vos entrées.\nLa structure ci-dessous affiche tous les labels qui seront créés et à quelles entrées ils seront appliqués.",
|
||||
"folders_to_tags.open_all": "Tout Ouvrir",
|
||||
@@ -42,18 +55,25 @@
|
||||
"generic.add": "Ajouter",
|
||||
"generic.apply": "Appliquer",
|
||||
"generic.cancel": "Annuler",
|
||||
"generic.copy": "Copier",
|
||||
"generic.cut": "Couper",
|
||||
"generic.delete": "Supprimer",
|
||||
"generic.done": "Terminé",
|
||||
"generic.edit": "Éditer",
|
||||
"generic.navigation.back": "Retour",
|
||||
"generic.navigation.next": "Suivant",
|
||||
"generic.paste": "Coller",
|
||||
"generic.recent_libraries": "Bibliothèques Récentes",
|
||||
"help.visit_github": "Visiter le Dépôt GitHub",
|
||||
"home.search": "Rechercher",
|
||||
"home.search_entries": "Recherche",
|
||||
"home.search_library": "Rechercher dans la Bibliothèque",
|
||||
"home.search_tags": "Recherche de Labels",
|
||||
"home.thumbnail_size": "Taille de la miniature",
|
||||
"ignore_list.add_extension": "Ajouter une Extension",
|
||||
"ignore_list.mode.exclude": "Exclure",
|
||||
"ignore_list.mode.include": "Inclure",
|
||||
"ignore_list.mode.label": "Mode Liste :",
|
||||
"ignore_list.mode.label": "Mode Liste",
|
||||
"ignore_list.title": "Extensions de Fichiers",
|
||||
"library.field.add": "Ajouter un Champ",
|
||||
"library.field.confirm_remove": "Êtes-vous sûr de vouloir supprimer le champ \"%{self.lib.get_field_attr(field, \"name\")}\" ?",
|
||||
@@ -61,23 +81,36 @@
|
||||
"library.field.remove": "Supprimer un Champ",
|
||||
"library.missing": "Emplacement Manquant",
|
||||
"library.name": "Bibliothèque",
|
||||
"library.refresh.scanning": "Recherche de Nouveaux Fichiers dans les Dossiers...\n%{x + 1} Fichiers%{\"s\" if x + 1 != 1 else \"\"} Recherchés, %{len(self.lib.files_not_in_library)} Nouveaux Fichiers Trouvés",
|
||||
"library.refresh.scanning_preparing": "Recherche de Nouveaux Fichiers dans les Dossiers...\nPréparation...",
|
||||
"library.refresh.title": "Rafraîchissement des Dossiers",
|
||||
"library.scan_library.title": "Balayage de la Bibliothèque",
|
||||
"macros.running.dialog.new_entries": "Éxectution des Macros Configurées sur %{x + 1}/%{len(new_ids)} Nouvelles Entrées",
|
||||
"macros.running.dialog.title": "Exécution des Macros sur les Nouvelles Entrées",
|
||||
"menu.edit": "Édition",
|
||||
"menu.edit.ignore_list": "Ignorer les Fichiers et Dossiers",
|
||||
"menu.file": "Fichier",
|
||||
"menu.file.new_library": "Nouvelle Bibliothéque",
|
||||
"menu.file.open_create_library": "Ouvrir/Créer une Bibliothèque",
|
||||
"menu.file.open_library": "Ouvrir la Bibliothèque",
|
||||
"menu.file.save_library": "Enregistrer la Bibliothèque",
|
||||
"menu.help": "Aide",
|
||||
"menu.macros": "Macros",
|
||||
"menu.select": "Sélectionner",
|
||||
"menu.tools": "Outils",
|
||||
"menu.view": "Vues",
|
||||
"menu.window": "Fenêtre",
|
||||
"preview.no_selection": "Pas d'Objet Selectionné",
|
||||
"select.all": "Tout Sélectionner",
|
||||
"select.clear": "Effacer la Sélection",
|
||||
"settings.open_library_on_start": "Ouvrir la Bibliothèque au Démarrage",
|
||||
"settings.show_filenames_in_grid": "Afficher les Noms de Fichiers en Grille",
|
||||
"settings.show_recent_libraries": "Afficher les Bibliothèques Récentes",
|
||||
"splash.opening_library": "Ouverture de la Bibliothèque",
|
||||
"status.library_backup_success": "Bibliothèque sauvegardée au chemin :",
|
||||
"status.library_save_success": "Bibliothèque Sauvegardée et Fermée !",
|
||||
"status.library_search_query": "Recherche dans la Bibliothèque pour",
|
||||
"status.results": "Résultats",
|
||||
"status.results_found": "{results.total_count} Résultats Trouvés",
|
||||
"tag.add": "Ajouter un Label",
|
||||
"tag.add_to_search": "Ajouter à la Recherche",
|
||||
"tag.aliases": "Alias",
|
||||
@@ -86,7 +119,13 @@
|
||||
"tag.new": "Nouveau Label",
|
||||
"tag.parent_tags": "Labels Parent",
|
||||
"tag.parent_tags.add": "Ajouter des Labels Parents",
|
||||
"tag.parent_tags.description": "Ce Tag peut être utilisé en replacement de tous ces Tags Parents dans les recherches.",
|
||||
"tag.search_for_tag": "Recherche de Label",
|
||||
"tag.shorthand": "Abrégé",
|
||||
"tag_manager.title": "Labels de la Bibliothèque"
|
||||
"tag_manager.title": "Labels de la Bibliothèque",
|
||||
"view.size.0": "Mini",
|
||||
"view.size.1": "Petit",
|
||||
"view.size.2": "Moyen",
|
||||
"view.size.3": "Grand",
|
||||
"view.size.4": "Très Grand"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
{
|
||||
"app.git": "Git-véglegesítés",
|
||||
"app.pre_release": "Kísérleti verzió",
|
||||
"edit.tag_manager": "Címkék kezelése",
|
||||
"entries.duplicate.merge": "Egyező elemek egyesítése",
|
||||
"entries.duplicate.merge.label": "Egyező elemek egyesítése",
|
||||
"entries.duplicate.refresh": "Egyező elemek frissítése",
|
||||
"entries.duplicates.description": "Ha több elem ugyanazzal a fájllal van összekapcsolva, akkor egyezőnek számítanak. Ha egyesíti őket, akkor egy olyan elem lesz létrehozva, ami az eredeti elemek összes adatát tartalmazza. Ezeket nem szabad összetéveszteni az „egyező fájlokkal”, amelyek a TagStudión kívüli azonos tartalmú fájlok.",
|
||||
@@ -19,6 +23,10 @@
|
||||
"entries.unlinked.scanning": "Kapcsolat nélküli elemek keresése a könyvtárban…",
|
||||
"entries.unlinked.search_and_relink": "Keresés és újra összekapcsolás",
|
||||
"entries.unlinked.title": "Kapcsolat nélküli elemek javítása",
|
||||
"field.copy": "Mező másolása",
|
||||
"field.edit": "Mező szerkesztése",
|
||||
"field.paste": "Mező beillesztése",
|
||||
"file.date_added": "Adatbázisba felvétel dátuma",
|
||||
"file.date_created": "Létrehozás dátuma",
|
||||
"file.date_modified": "Módosítás dátuma",
|
||||
"file.dimensions": "Méret",
|
||||
@@ -32,9 +40,14 @@
|
||||
"file.duplicates.matches_uninitialized": "Nincsenek egyező fájlok",
|
||||
"file.duplicates.mirror.description": "Az összes adat átmásolása minden összetartozó fájl között, ezzel kiegészítve a hiányzó címkéket eltávolítás és duplikálás nélkül. Ez a folyamat nem fog adatokat vagy fájlokat törölni.",
|
||||
"file.duplicates.mirror_entries": "Elemek tükrözése",
|
||||
"file.duration": "Hossz",
|
||||
"file.not_found": "Az alábbi fájl nem található:",
|
||||
"file.open_file": "Fájl megnyitása",
|
||||
"file.open_file_with": "Társítás",
|
||||
"file.open_location.generic": "Fájl megnyitása Intézőben",
|
||||
"file.open_location.mac": "Megnyitás Finderben",
|
||||
"file.open_location.windows": "Megnyitás Intézőben",
|
||||
"folders_to_tags.close_all": "Összes bezárása",
|
||||
"folders_to_tags.converting": "Mappák címkékké alakítása",
|
||||
"folders_to_tags.description": "Címkék automatikus létrehozása a létező mappastruktúra alapján.\nAz alábbi mappafán megtekintheti a létrehozandó címkéket, és hogy mely elemekre lesznek alkalmazva.",
|
||||
"folders_to_tags.open_all": "Összes megnyitása",
|
||||
@@ -42,12 +55,19 @@
|
||||
"generic.add": "Hozzáadás",
|
||||
"generic.apply": "Alkalmaz",
|
||||
"generic.cancel": "Mégse",
|
||||
"generic.copy": "Másolás",
|
||||
"generic.cut": "Kivágás",
|
||||
"generic.delete": "Törlés",
|
||||
"generic.done": "Kész",
|
||||
"generic.edit": "Szerkesztés",
|
||||
"generic.navigation.back": "Vissza",
|
||||
"generic.navigation.next": "Tovább",
|
||||
"generic.paste": "Beillesztés",
|
||||
"generic.recent_libraries": "Legutóbbi könytárak",
|
||||
"help.visit_github": "GitHub-adattár megnyitása",
|
||||
"home.search": "Keresés",
|
||||
"home.search_entries": "Tételek keresése",
|
||||
"home.search_library": "Keresés a könyvtárban",
|
||||
"home.search_tags": "Címkék keresése",
|
||||
"home.thumbnail_size": "Indexkép mérete",
|
||||
"ignore_list.add_extension": "Kiterjesztés hozzáadása",
|
||||
@@ -61,23 +81,36 @@
|
||||
"library.field.remove": "Mező eltávolítása",
|
||||
"library.missing": "Hiányzó hely",
|
||||
"library.name": "Könyvtár",
|
||||
"library.refresh.scanning": "Új fájlok keresése a mappákban…\n%{x + 1} fájl megvizsgálva; ebből %{len(self.lib.files_not_in_library)} új",
|
||||
"library.refresh.scanning_preparing": "Új fájlok keresése a mappákban…\nElőkészítés…",
|
||||
"library.refresh.title": "Mappák frissítése",
|
||||
"library.scan_library.title": "Könyvtár vizsgálata",
|
||||
"macros.running.dialog.new_entries": "Korábban beállított makrók futtatása %{len(new_ids)}/%{x + 1} új elemen",
|
||||
"macros.running.dialog.title": "Makrók futtatása az új elemeken",
|
||||
"menu.edit": "Szerkesztés",
|
||||
"menu.edit.ignore_list": "Fájlok és mappák figyelmen kívül hagyása",
|
||||
"menu.file": "Fájl",
|
||||
"menu.file.new_library": "Új könyvtár",
|
||||
"menu.file.open_create_library": "Könyvtár megnyitása/létrehozása",
|
||||
"menu.file.open_library": "Könyvtár megnyitása",
|
||||
"menu.file.save_library": "Könyvtár mentése",
|
||||
"menu.help": "Súgó",
|
||||
"menu.macros": "Makrók",
|
||||
"menu.select": "Kijelölés",
|
||||
"menu.tools": "Eszközök",
|
||||
"menu.view": "Nézet",
|
||||
"menu.window": "Ablak",
|
||||
"preview.no_selection": "Nincs kijelölt elem",
|
||||
"select.all": "Összes kijelölése",
|
||||
"select.clear": "Kijelölés megszüntetése",
|
||||
"settings.open_library_on_start": "Könyvtár megnyitása a program indulásakor",
|
||||
"settings.show_filenames_in_grid": "Fájlnevek megjelenítése rácsnézetben",
|
||||
"settings.show_recent_libraries": "Legutóbbi könyvtárak megjelenítése",
|
||||
"splash.opening_library": "Könyvtár megnyitása folyamatban…",
|
||||
"status.library_backup_success": "A biztonsági mentés létrehozása megtörtént az alábbi elérési úton:",
|
||||
"status.library_save_success": "A könyvtár mentése és bezárása sikeresen megtörtént.",
|
||||
"status.library_search_query": "Az alábbi kifejezés keresése a könyvtárban:",
|
||||
"status.results": "találat",
|
||||
"status.results_found": "{results.total_count} találat",
|
||||
"tag.add": "Címke hozzáadása",
|
||||
"tag.add_to_search": "Keresési kifejezés kiegészítése",
|
||||
"tag.aliases": "Áljelek",
|
||||
@@ -86,7 +119,13 @@
|
||||
"tag.new": "Új címke",
|
||||
"tag.parent_tags": "Szülőcímkék",
|
||||
"tag.parent_tags.add": "Új szülőcímke",
|
||||
"tag.parent_tags.description": "Ez a címke képes helyettesíteni bármely alábbi szülőcímkét kereséskor.",
|
||||
"tag.search_for_tag": "Címke keresése",
|
||||
"tag.shorthand": "Rövidítés",
|
||||
"tag_manager.title": "Könyvtárcímkék"
|
||||
"tag_manager.title": "Könyvtárcímkék",
|
||||
"view.size.0": "Apró",
|
||||
"view.size.1": "Kicsi",
|
||||
"view.size.2": "Közepes",
|
||||
"view.size.3": "Nagy",
|
||||
"view.size.4": "Extra nagy"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
{
|
||||
"app.git": "Migawki Git",
|
||||
"app.pre_release": "Przedpremiera",
|
||||
"edit.tag_manager": "Zarządzaj tagami",
|
||||
"entries.duplicate.merge": "Złącz zduplikowane wpisy",
|
||||
"entries.duplicate.merge.label": "Łączenie zduplikowanych wpisów",
|
||||
"entries.duplicate.refresh": "Odśwież zduplikowane wpisy",
|
||||
"entries.duplicates.description": "Zduplikowane wpisy są zdefiniowane jako wielokrotne wpisy które wskazują na ten sam plik na dysku. Złączenie ich złączy tagi i metadane ze wszystkich duplikatów w jeden wpis. Nie mylić tego ze \"zduplikowanymi plikami\" które są duplikatami samych plików poza TagStudio.",
|
||||
@@ -17,7 +21,12 @@
|
||||
"entries.unlinked.relink.manual": "Ręczne ponowne łączenie",
|
||||
"entries.unlinked.relink.title": "Ponowne łączenie wpisów",
|
||||
"entries.unlinked.scanning": "Skanowanie biblioteki dla odłączonych wpisów...",
|
||||
"entries.unlinked.search_and_relink": "Wyszukaj && Zalinkuj ponownie",
|
||||
"entries.unlinked.title": "Napraw odłączone wpisy",
|
||||
"field.copy": "Skopiuj pole",
|
||||
"field.edit": "Edytuj pole",
|
||||
"field.paste": "Wklej pole",
|
||||
"file.date_added": "Data dodania",
|
||||
"file.date_created": "Data utworzenia",
|
||||
"file.date_modified": "Data modyfikacji",
|
||||
"file.dimensions": "Wymiary",
|
||||
@@ -31,27 +40,40 @@
|
||||
"file.duplicates.matches_uninitialized": "Dopasowania zduplikowanych plików: Brak",
|
||||
"file.duplicates.mirror.description": "Odzwierciedla dane wpisu w każdym zduplikowanym zestawie, łącząc wszystkie dane nie usuwając i nie duplikując pól. Ta operacja nie usunie żadnych plików ani danych.",
|
||||
"file.duplicates.mirror_entries": "Odzwierciedl wpisy",
|
||||
"file.not_found": "Nie znaleziono pliku:",
|
||||
"file.duration": "Czas trwania",
|
||||
"file.not_found": "Nie znaleziono pliku",
|
||||
"file.open_file": "Otwórz plik",
|
||||
"file.open_location.generic": "Otwórz plik w przeglądarce plików",
|
||||
"file.open_file_with": "Otwórz plik używając",
|
||||
"file.open_location.generic": "Pokaż plik w przeglądarce plików",
|
||||
"file.open_location.mac": "Pokaż plik w Finderze",
|
||||
"file.open_location.windows": "Pokaż plik w Eksploratorze plików",
|
||||
"folders_to_tags.close_all": "Zamknij wszystko",
|
||||
"folders_to_tags.converting": "Konwertowanie folderów na tagi",
|
||||
"folders_to_tags.description": "Tworzy tagi na podstawie struktury folderów i stosuje je do wpisów.\n Poniższa struktura przedstawia wszystkie utworzone tagi i wpisy, do których zostaną one zastosowane.",
|
||||
"folders_to_tags.open_all": "Otwórz wszystkie",
|
||||
"folders_to_tags.title": "Stwórz tagi z folderów",
|
||||
"generic.add": "Dodaj",
|
||||
"generic.apply": "Zastosuj",
|
||||
"generic.cancel": "Anuluj",
|
||||
"generic.copy": "Kopiuj",
|
||||
"generic.cut": "Wytnij",
|
||||
"generic.delete": "Usuń",
|
||||
"generic.done": "Zrobione",
|
||||
"generic.edit": "Edytuj",
|
||||
"generic.navigation.back": "Wstecz",
|
||||
"generic.navigation.next": "Dalej",
|
||||
"generic.paste": "Wklej",
|
||||
"generic.recent_libraries": "Ostatnie biblioteki",
|
||||
"help.visit_github": "Odwiedź repozytorium GitHub",
|
||||
"home.search": "Szukaj",
|
||||
"home.search_entries": "Przeszukaj wpisy",
|
||||
"home.search_library": "Przeszukaj bibliotekę",
|
||||
"home.search_tags": "Przeszukaj tagi",
|
||||
"home.thumbnail_size": "Rozmiar miniaturek",
|
||||
"ignore_list.add_extension": "Dodaj rozszerzenie",
|
||||
"ignore_list.mode.exclude": "Wyklucz",
|
||||
"ignore_list.mode.include": "Uwzględnij",
|
||||
"ignore_list.mode.label": "Tryb listy:",
|
||||
"ignore_list.mode.label": "Tryb listy",
|
||||
"ignore_list.title": "Rozszerzenia pliku",
|
||||
"library.field.add": "Dodaj pole",
|
||||
"library.field.confirm_remove": "Jesteś pewien że chcesz usunąć pole \"%{self.lib.get_field_attr(field, \"name\")}\" ?",
|
||||
@@ -59,23 +81,36 @@
|
||||
"library.field.remove": "Usuń pole",
|
||||
"library.missing": "Brak lokalizacji",
|
||||
"library.name": "Biblioteka",
|
||||
"library.refresh.scanning": "Skanowanie katalogów w celu znalezienia nowych plików...\n%{x + 1} File%{\"s\" if x + 1 != 1 else \"\"} Przeszukano, Znaleziono %{len(self.lib.files_not_in_library)} nowych plików",
|
||||
"library.refresh.scanning_preparing": "Skanowanie katalogów w poszukiwaniu nowych plików\nPrzygotowywanie...",
|
||||
"library.refresh.title": "Odświeżanie katalogów",
|
||||
"library.scan_library.title": "Skanowanie biblioteki",
|
||||
"macros.running.dialog.new_entries": "Stosowanie skonfigurowanych makr na 1/%{len(new_ids)} nowych wpisach",
|
||||
"macros.running.dialog.title": "Stosowanie makr na nowych wpisach",
|
||||
"menu.edit": "Edytuj",
|
||||
"menu.edit.ignore_list": "Ignoruj pliki i foldery",
|
||||
"menu.file": "Plik",
|
||||
"menu.file.new_library": "Nowa biblioteka",
|
||||
"menu.file.open_create_library": "Otwórz/Stwórz bibliotekę",
|
||||
"menu.file.open_library": "Otwórz bibliotekę",
|
||||
"menu.file.save_library": "Zapisz bibliotekę",
|
||||
"menu.help": "Pomoc",
|
||||
"menu.macros": "Makra",
|
||||
"menu.select": "Zaznacz",
|
||||
"menu.tools": "Narzędzia",
|
||||
"menu.view": "Widok",
|
||||
"menu.window": "Okno",
|
||||
"preview.no_selection": "Nie wybrano żadnych pozycji",
|
||||
"select.all": "Zaznacz wszystko",
|
||||
"select.clear": "Odznacz zaznaczenie",
|
||||
"settings.open_library_on_start": "Otwieraj bibliotekę podczas startu",
|
||||
"settings.show_filenames_in_grid": "Pokazuj nazwy plików w siatce",
|
||||
"settings.show_recent_libraries": "Pokazuj ostatnie biblioteki",
|
||||
"splash.opening_library": "Otwieranie biblioteki",
|
||||
"status.library_backup_success": "Kopia zapasowa biblioteki zapisana w:",
|
||||
"status.library_save_success": "Biblioteka zapisana i zamknięta!",
|
||||
"status.library_search_query": "Przeszukiwanie biblioteki dla",
|
||||
"status.results": "Wyniki",
|
||||
"status.results_found": "Znaleziono {results.total_count} wyników",
|
||||
"tag.add": "Dodaj tag",
|
||||
"tag.add_to_search": "Dodaj do wyszukiwania",
|
||||
"tag.aliases": "Aliasy",
|
||||
@@ -84,7 +119,13 @@
|
||||
"tag.new": "Nowy tag",
|
||||
"tag.parent_tags": "Tagi nadrzędne",
|
||||
"tag.parent_tags.add": "Dodaj tagi nadrzędne",
|
||||
"tag.parent_tags.description": "Ten tag może być traktowany w wyszukiwaniach jako zamiennik dla dowolnego z tych tagów nadrzędnych.",
|
||||
"tag.search_for_tag": "Szukaj dla tagu",
|
||||
"tag.shorthand": "Skrót",
|
||||
"tag_manager.title": "Biblioteka tagów"
|
||||
"tag_manager.title": "Biblioteka tagów",
|
||||
"view.size.0": "Mini",
|
||||
"view.size.1": "Mały",
|
||||
"view.size.2": "Średni",
|
||||
"view.size.3": "Duży",
|
||||
"view.size.4": "Bardzo duży"
|
||||
}
|
||||
|
||||
@@ -61,7 +61,6 @@
|
||||
"library.field.remove": "Remover Campo",
|
||||
"library.missing": "Localização Ausente",
|
||||
"library.name": "Biblioteca",
|
||||
"library.refresh.scanning": "Escaneando Diretórios por Novos Arquivos...\n%{x + 1} Arquivo%{\"s\" if x + 1 != 1 else \"\"} Pesquisado%{\"s\" if x + 1 != 1 else \"\"}, %{len(self.lib.files_not_in_library)} Novo(s) Arquivo(s) Encontrado(s)",
|
||||
"library.refresh.scanning_preparing": "Escaneando Diretórios por Novos Arquivos...\nPreparando...",
|
||||
"library.refresh.title": "Atualizando Diretórios",
|
||||
"library.scan_library.title": "Escaneando Biblioteca",
|
||||
|
||||
@@ -61,7 +61,6 @@
|
||||
"library.field.remove": "Удалить Категорию",
|
||||
"library.missing": "Путь не найден",
|
||||
"library.name": "Библиотека",
|
||||
"library.refresh.scanning": "Сканирование на наличие новых файлов...\n%{x + 1} File%{\"s\" if x + 1 != 1 else \"\"} Searched, %{len(self.lib.files_not_in_library)} Найдены новые файлы",
|
||||
"library.refresh.scanning_preparing": "Сканирование каталога на наличие новых файлов...\nПодготовка...",
|
||||
"library.refresh.title": "Обновление Каталога",
|
||||
"library.scan_library.title": "Сканирование Библиотеки",
|
||||
|
||||
@@ -61,7 +61,6 @@
|
||||
"library.field.remove": "புலத்தை அகற்று",
|
||||
"library.missing": "இடம் காணவில்லை",
|
||||
"library.name": "நூலகம்",
|
||||
"library.refresh.scanning": "புதிய கோப்புகளுக்கான அடைவுகள் சோதனை செய்யப்படுகின்றது...\n%{x + 1} கோப்பு%{\"s\" if x + 1 != 1 else \"\"} தேடப்பட்டது, %{len(self.lib.files_not_in_library)} புதிய கோப்புகள் கிடைத்தன",
|
||||
"library.refresh.scanning_preparing": "புதிய கோப்புகளுக்கான அடைவுகள் சோதனை செய்யப்படுகின்றது...\nதயாராகிறது...",
|
||||
"library.refresh.title": "கோப்பகங்கள் புதுப்பிக்கப்படுகின்றன",
|
||||
"library.scan_library.title": "புத்தககல்லரி சோதனை செய்யப்படுகிறது",
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
{
|
||||
"app.git": "Git Commit",
|
||||
"app.pre_release": "ilo pi pakala lili",
|
||||
"edit.tag_manager": "o lawa e poki",
|
||||
"entries.duplicate.merge": "o wan e ijo sama",
|
||||
"entries.duplicate.merge.label": "ijo sama li kama wan",
|
||||
"entries.duplicate.refresh": "o kama jo e sona tan ijo sama",
|
||||
"entries.duplicates.description": "ken la, ijo mute li jo e ijo lon sama. ni li \"ijo sama\". sina wan e ona la, ijo sama li kama wan li jo e sona ale tan ijo sama ale.",
|
||||
@@ -19,6 +23,10 @@
|
||||
"entries.unlinked.scanning": "mi o alasa e ijo pi ijo lon ala...",
|
||||
"entries.unlinked.search_and_relink": "o alasa o pana e ijo lon tawa ijo",
|
||||
"entries.unlinked.title": "o pona e ijo pi ijo lon ala",
|
||||
"field.copy": "o jo e sona sama",
|
||||
"field.edit": "o ante e sona",
|
||||
"field.paste": "o pana e sona sama",
|
||||
"file.date_added": "tenpo pi kama namako",
|
||||
"file.date_created": "tenpo pi kama sin",
|
||||
"file.date_modified": "tenpo pi kama ante",
|
||||
"file.dimensions": "suli",
|
||||
@@ -32,9 +40,14 @@
|
||||
"file.duplicates.matches_uninitialized": "ijo sama: ala",
|
||||
"file.duplicates.mirror.description": "o jasima e sona kama lon kulupu sama ale. sona ale li kama wan li weka ala li kama mute ala. ni li weka ala e sona e ijo.",
|
||||
"file.duplicates.mirror_entries": "o jasima e ijo",
|
||||
"file.not_found": "mi ken ala alasa e ijo:",
|
||||
"file.duration": "suli tenpo",
|
||||
"file.not_found": "mi ken ala alasa e ijo",
|
||||
"file.open_file": "open e ijo",
|
||||
"file.open_file_with": "o lukin e ijo kepeken...",
|
||||
"file.open_location.generic": "open e ijo lon ilo alasa",
|
||||
"file.open_location.mac": "o alasa lon ilo Finder",
|
||||
"file.open_location.windows": "o alasa lon ilo File Explorer",
|
||||
"folders_to_tags.close_all": "o pini e lukin pi ijo ale",
|
||||
"folders_to_tags.converting": "mi o pali e poki tan poki tomo",
|
||||
"folders_to_tags.description": "ni li pali e poki tan poki tona li pana e poki sin tawa ijo lon tomo.\n ilo ni li pali e anpa.",
|
||||
"folders_to_tags.open_all": "open e ale",
|
||||
@@ -42,18 +55,25 @@
|
||||
"generic.add": "o pana",
|
||||
"generic.apply": "o pana",
|
||||
"generic.cancel": "o ala",
|
||||
"generic.copy": "o jo e sona sama",
|
||||
"generic.cut": "o lanpan",
|
||||
"generic.delete": "o weka",
|
||||
"generic.done": "pona",
|
||||
"generic.edit": "ante",
|
||||
"generic.navigation.back": "o tawa weka",
|
||||
"generic.navigation.next": "o tawa poka",
|
||||
"generic.paste": "o pana e sona sama",
|
||||
"generic.recent_libraries": "tomo pi tenpo poka",
|
||||
"help.visit_github": "o tawa linluwi GitHub Repository",
|
||||
"home.search": "alasa",
|
||||
"home.search_entries": "ijo alasa",
|
||||
"home.search_library": "o alasa lon tomo",
|
||||
"home.search_tags": "o alasa e poki",
|
||||
"home.thumbnail_size": "suli sitelen",
|
||||
"ignore_list.add_extension": "o pana e nimi anpa",
|
||||
"ignore_list.mode.exclude": "o kepeken ala",
|
||||
"ignore_list.mode.include": "o kepeken",
|
||||
"ignore_list.mode.label": "nasin kulupu:",
|
||||
"ignore_list.mode.label": "nasin kulupu",
|
||||
"ignore_list.title": "nimi lon nimi ijo anpa",
|
||||
"library.field.add": "pana e sona",
|
||||
"library.field.confirm_remove": "sina weka e sona poki \"%{self.lib.get_field_attr(field, \"name\")}\". ni li pona anu seme?",
|
||||
@@ -61,23 +81,33 @@
|
||||
"library.field.remove": "weka e sona",
|
||||
"library.missing": "ma li lon ala",
|
||||
"library.name": "tomo",
|
||||
"library.refresh.scanning": "mi alasa e ijo sin lon tomo...\nmi lukin e ijo %{x + 1}. ijo %{len(self.lib.files_not_in_library)} li sin",
|
||||
"library.refresh.scanning_preparing": "mi alasa e ijo sin lon tomo...\nmi kama pona...",
|
||||
"library.refresh.title": "mi kama jo e sin lon tomo",
|
||||
"library.scan_library.title": "mi o lukin e tomo",
|
||||
"macros.running.dialog.new_entries": "mi pali lon ijo sin %{x + 1}/%{len(new_ids)}",
|
||||
"macros.running.dialog.title": "mi pali lon ijo sin",
|
||||
"menu.edit": "ante",
|
||||
"menu.edit.ignore_list": "o sona ala e ijo ni",
|
||||
"menu.file": "ijo",
|
||||
"menu.file.new_library": "o sin e tomo",
|
||||
"menu.file.open_create_library": "o open/sin e tomo",
|
||||
"menu.file.open_library": "o open e tomo",
|
||||
"menu.file.save_library": "o awen e sona tomo",
|
||||
"menu.help": "mi jo e toki seme",
|
||||
"menu.macros": "ilo pali",
|
||||
"menu.tools": "ilo",
|
||||
"menu.view": "o lukin",
|
||||
"menu.window": "lipu",
|
||||
"preview.no_selection": "ijo ala li anu",
|
||||
"settings.open_library_on_start": "ilo Tagstudio li open la o open e tomo ni",
|
||||
"settings.show_filenames_in_grid": "o sitelen e nimi ijo lon leko sitelen",
|
||||
"settings.show_recent_libraries": "o sitelen e tomo pi tenpo poka",
|
||||
"splash.opening_library": "mi open e tomo",
|
||||
"status.library_backup_success": "tomo sama li lon:",
|
||||
"status.library_save_success": "tomo li awen li weka!",
|
||||
"status.library_search_query": "mi alasa e",
|
||||
"status.results": "jo",
|
||||
"status.results_found": "mi sona e ijo {results.total_count}",
|
||||
"tag.add": "o pana e poki",
|
||||
"tag.add_to_search": "pana tawa alasa",
|
||||
"tag.aliases": "nimi ante",
|
||||
@@ -86,7 +116,13 @@
|
||||
"tag.new": "poki sin",
|
||||
"tag.parent_tags": "poki mama",
|
||||
"tag.parent_tags.add": "o pana e poki mama",
|
||||
"tag.parent_tags.description": "alasa la, poki ni li ken sama poki mama.",
|
||||
"tag.search_for_tag": "o alasa e poki",
|
||||
"tag.shorthand": "nimi lili",
|
||||
"tag_manager.title": "poki tomo"
|
||||
"tag_manager.title": "poki tomo",
|
||||
"view.size.0": "lili a",
|
||||
"view.size.1": "lili",
|
||||
"view.size.2": "meso",
|
||||
"view.size.3": "suli",
|
||||
"view.size.4": "suli a"
|
||||
}
|
||||
|
||||
@@ -61,7 +61,6 @@
|
||||
"library.field.remove": "Alan Kaldır",
|
||||
"library.missing": "Lokasyon bulunamadı",
|
||||
"library.name": "Kütüphane",
|
||||
"library.refresh.scanning": "Yeni Dosyalar için Dizinler Taranıyor...\n%{x + 1} File%{\"s\" if x + 1 != 1 else \"\"} Arandı, %{len(self.lib.files_not_in_library)} Yeni Dosya Bulundu",
|
||||
"library.refresh.scanning_preparing": "Yeni Dosyalar için Dizinler Taranıyor...\nHazırlanıyor...",
|
||||
"library.refresh.title": "Dizinler Yenileniyor",
|
||||
"library.scan_library.title": "Kütüphane Taranıyor",
|
||||
|
||||
129
tagstudio/resources/translations/zh_Hant.json
Normal file
129
tagstudio/resources/translations/zh_Hant.json
Normal file
@@ -0,0 +1,129 @@
|
||||
{
|
||||
"app.git": "Git提交更新",
|
||||
"app.pre_release": "預先發行",
|
||||
"edit.tag_manager": "標籤管理",
|
||||
"entries.duplicate.merge": "合併重複項目",
|
||||
"entries.duplicate.merge.label": "正在合併重複項目",
|
||||
"entries.duplicate.refresh": "重新整理重複項目",
|
||||
"entries.duplicates.description": "重複的項目指的是多個項目皆指向同一個檔案。合併這些重複項目會合併所有項目的標籤和後設資料。和重複檔案不一樣,重複檔案室在Tag Studio外重複的檔案。",
|
||||
"entries.mirror": "鏡像",
|
||||
"entries.mirror.confirmation": "您確定要鏡像以下 %{len(self.lib.dupe_files)} 項目?",
|
||||
"entries.mirror.label": "正在鏡像 1/%{count} 項目...",
|
||||
"entries.mirror.title": "正在鏡像項目",
|
||||
"entries.tags": "標籤",
|
||||
"entries.unlinked.delete": "刪除未連結的項目",
|
||||
"entries.unlinked.delete.confirm": "您確定要刪除這些 %{len(self.lib.missing_files)} 項目?",
|
||||
"entries.unlinked.delete.deleting": "正在刪除項目",
|
||||
"entries.unlinked.delete.deleting_count": "正在刪除 %{x[0]+1}/{len(self.lib.missing_files)} 未連結的項目",
|
||||
"entries.unlinked.refresh_all": "重新整理",
|
||||
"entries.unlinked.relink.attempting": "正在重新連結 %{x[0]+1}/%{len(self.lib.missing_files)} 項目, %{self.fixed} 重新連結成功",
|
||||
"entries.unlinked.relink.manual": "手動重新連結",
|
||||
"entries.unlinked.relink.title": "正在重新連結項目",
|
||||
"entries.unlinked.scanning": "正在掃描資料庫以尋找未連結的項目...",
|
||||
"entries.unlinked.search_and_relink": "搜尋並重新連結",
|
||||
"entries.unlinked.title": "修復未連結項目",
|
||||
"field.copy": "複製欄位",
|
||||
"field.edit": "編輯欄位",
|
||||
"field.paste": "貼上欄位",
|
||||
"file.date_added": "新增日期",
|
||||
"file.date_created": "建立日期",
|
||||
"file.date_modified": "更改日期",
|
||||
"file.dimensions": "尺寸",
|
||||
"file.duplicates.dupeguru.file_extension": "DupeGuru 檔案 (*.dupeguru)",
|
||||
"file.duplicates.dupeguru.load_file": "匯入 DupeGuru 檔案",
|
||||
"file.duplicates.dupeguru.no_file": "尚未選擇 DupeGuru 檔案",
|
||||
"file.duplicates.dupeguru.open_file": "開啟 DupeGuru 結果檔案",
|
||||
"file.duplicates.fix": "修復重複檔案",
|
||||
"file.duplicates.matches": "重複檔案: %{count}",
|
||||
"file.duplicates.matches_uninitialized": "重複檔案: 無",
|
||||
"file.duplicates.mirror.description": "將項目資料鏡像至各 duplicate match set,在不刪除或是重複欄位的狀態下合併所有資料。此動作不會刪除任何欄位或資料。",
|
||||
"file.duplicates.mirror_entries": "項目鏡像",
|
||||
"file.duration": "長度",
|
||||
"file.not_found": "找不到檔案",
|
||||
"file.open_file": "開啟檔案",
|
||||
"file.open_file_with": "開啟檔案",
|
||||
"file.open_location.generic": "在檔案總管內顯示",
|
||||
"file.open_location.mac": "在Finder顯示",
|
||||
"file.open_location.windows": "在檔案總管內顯示",
|
||||
"folders_to_tags.close_all": "關閉全部",
|
||||
"folders_to_tags.converting": "正在將資料夾轉換成標籤",
|
||||
"folders_to_tags.description": "依照您的資料夾結構建立標籤並套用到項目上。\n. 以下的結構提供所有會建立的標籤和會透用到哪些項目上。",
|
||||
"folders_to_tags.open_all": "開啟全部",
|
||||
"folders_to_tags.title": "從資料夾建立標籤",
|
||||
"generic.add": "新增",
|
||||
"generic.apply": "套用",
|
||||
"generic.cancel": "取消",
|
||||
"generic.copy": "複製",
|
||||
"generic.cut": "剪下",
|
||||
"generic.delete": "刪除",
|
||||
"generic.done": "完成",
|
||||
"generic.edit": "編輯",
|
||||
"generic.navigation.back": "回到",
|
||||
"generic.navigation.next": "下一個",
|
||||
"generic.paste": "貼上",
|
||||
"generic.recent_libraries": "最近使用資料庫",
|
||||
"help.visit_github": "訪問 GitHub 儲存庫",
|
||||
"home.search": "搜尋",
|
||||
"home.search_entries": "搜尋項目",
|
||||
"home.search_library": "搜尋資料庫",
|
||||
"home.search_tags": "搜尋標籤",
|
||||
"home.thumbnail_size": "縮圖大小",
|
||||
"ignore_list.add_extension": "新增擴充功能",
|
||||
"ignore_list.mode.exclude": "排除",
|
||||
"ignore_list.mode.include": "包含",
|
||||
"ignore_list.mode.label": "條列模式",
|
||||
"ignore_list.title": "檔案格式",
|
||||
"library.field.add": "新增欄位",
|
||||
"library.field.confirm_remove": "您確定要移除此 \"%{self.lib.get_field_attr(field, \"name\")}\" 欄位?",
|
||||
"library.field.mixed_data": "混合資料",
|
||||
"library.field.remove": "移除欄位",
|
||||
"library.missing": "找不到資料庫路徑",
|
||||
"library.name": "資料庫",
|
||||
"library.refresh.scanning_preparing": "正在掃描資料夾中的新檔案...\n準備中...",
|
||||
"library.refresh.title": "重新整理路徑中",
|
||||
"library.scan_library.title": "掃描資料庫中",
|
||||
"macros.running.dialog.new_entries": "執行巨集中找到 %{x + 1}/%{len(new_ids)} 個新項目",
|
||||
"macros.running.dialog.title": "針對新項目執行巨集",
|
||||
"menu.edit": "編輯",
|
||||
"menu.edit.ignore_list": "略過檔案和資料夾",
|
||||
"menu.file": "檔案",
|
||||
"menu.file.new_library": "新資料庫",
|
||||
"menu.file.open_create_library": "開啟/建立 Library",
|
||||
"menu.file.open_library": "開啟資料庫",
|
||||
"menu.file.save_library": "儲存儲存庫",
|
||||
"menu.help": "提示",
|
||||
"menu.macros": "巨集",
|
||||
"menu.select": "選擇",
|
||||
"menu.tools": "工具",
|
||||
"menu.view": "檢視",
|
||||
"menu.window": "選單",
|
||||
"preview.no_selection": "尚未選擇物件",
|
||||
"select.all": "全選",
|
||||
"select.clear": "清除選項",
|
||||
"settings.open_library_on_start": "在啟動時開啟儲存庫",
|
||||
"settings.show_filenames_in_grid": "在Grid顯示檔案名稱",
|
||||
"settings.show_recent_libraries": "顯示最近使用過儲存庫",
|
||||
"splash.opening_library": "開啟儲存庫",
|
||||
"status.library_backup_success": "儲存庫備份路徑:",
|
||||
"status.library_save_success": "資料庫保存成功!",
|
||||
"status.library_search_query": "正在搜尋儲存庫",
|
||||
"status.results": "結果",
|
||||
"status.results_found": "找到 {results.total_count} 個結果",
|
||||
"tag.add": "新增標籤",
|
||||
"tag.add_to_search": "新增到搜尋",
|
||||
"tag.aliases": "別名",
|
||||
"tag.color": "顏色",
|
||||
"tag.name": "名稱",
|
||||
"tag.new": "新標籤",
|
||||
"tag.parent_tags": "上層標籤",
|
||||
"tag.parent_tags.add": "新增上層標籤",
|
||||
"tag.parent_tags.description": "此標籤可以替代以下的上層標籤。",
|
||||
"tag.search_for_tag": "尋找標籤",
|
||||
"tag.shorthand": "速記",
|
||||
"tag_manager.title": "儲存庫標籤",
|
||||
"view.size.0": "迷你",
|
||||
"view.size.1": "小",
|
||||
"view.size.2": "中",
|
||||
"view.size.3": "大",
|
||||
"view.size.4": "特大"
|
||||
}
|
||||
@@ -16,7 +16,7 @@ class DriverMixin:
|
||||
"""Check if the path of library is valid."""
|
||||
library_path: Path | None = None
|
||||
if open_path:
|
||||
library_path = Path(open_path)
|
||||
library_path = Path(open_path).expanduser()
|
||||
if not library_path.exists():
|
||||
logger.error("Path does not exist.", open_path=open_path)
|
||||
return LibraryStatus(success=False, message="Path does not exist.")
|
||||
|
||||
@@ -543,10 +543,18 @@ class Library:
|
||||
statement = select(Entry)
|
||||
|
||||
if search.ast:
|
||||
start_time = time.time()
|
||||
|
||||
statement = statement.outerjoin(Entry.tag_box_fields).where(
|
||||
SQLBoolExpressionBuilder(self).visit(search.ast)
|
||||
)
|
||||
|
||||
end_time = time.time()
|
||||
|
||||
logger.info(
|
||||
f"SQL Expression Builder finished ({format_timespan(end_time - start_time)})"
|
||||
)
|
||||
|
||||
extensions = self.prefs(LibraryPrefs.EXTENSION_LIST)
|
||||
is_exclude_list = self.prefs(LibraryPrefs.IS_EXCLUDE_LIST)
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from sqlalchemy import and_, distinct, func, or_, select
|
||||
import structlog
|
||||
from sqlalchemy import and_, distinct, func, or_, select, text
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.sql.expression import BinaryExpression, ColumnExpressionArgument
|
||||
from src.core.media_types import FILETYPE_EQUIVALENTS, MediaCategories
|
||||
@@ -16,6 +17,20 @@ if TYPE_CHECKING:
|
||||
else:
|
||||
Library = None # don't import .library because of circular imports
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
CHILDREN_QUERY = text("""
|
||||
-- Note for this entire query that tag_subtags.child_id is the parent id and tag_subtags.parent_id is the child id due to bad naming
|
||||
WITH RECURSIVE Subtags AS (
|
||||
SELECT :tag_id AS child_id
|
||||
UNION ALL
|
||||
SELECT ts.parent_id AS child_id
|
||||
FROM tag_subtags ts
|
||||
INNER JOIN Subtags s ON ts.child_id = s.child_id
|
||||
)
|
||||
SELECT * FROM Subtags;
|
||||
""") # noqa: E501
|
||||
|
||||
|
||||
def get_filetype_equivalency_list(item: str) -> list[str] | set[str]:
|
||||
for s in FILETYPE_EQUIVALENTS:
|
||||
@@ -98,16 +113,28 @@ class SQLBoolExpressionBuilder(BaseVisitor[ColumnExpressionArgument]):
|
||||
def visit_not(self, node: Not) -> ColumnExpressionArgument:
|
||||
return ~self.__entry_satisfies_ast(node.child)
|
||||
|
||||
def __get_tag_ids(self, tag_name: str) -> list[int]:
|
||||
def __get_tag_ids(self, tag_name: str, include_children: bool = True) -> list[int]:
|
||||
"""Given a tag name find the ids of all tags that this name could refer to."""
|
||||
with Session(self.lib.engine, expire_on_commit=False) as session:
|
||||
return list(
|
||||
with Session(self.lib.engine) as session:
|
||||
tag_ids = list(
|
||||
session.scalars(
|
||||
select(Tag.id)
|
||||
.where(or_(Tag.name.ilike(tag_name), Tag.shorthand.ilike(tag_name)))
|
||||
.union(select(TagAlias.tag_id).where(TagAlias.name.ilike(tag_name)))
|
||||
)
|
||||
)
|
||||
if len(tag_ids) > 1:
|
||||
logger.debug(
|
||||
f'Tag Constraint "{tag_name}" is ambiguous, {len(tag_ids)} matching tags found',
|
||||
tag_ids=tag_ids,
|
||||
include_children=include_children,
|
||||
)
|
||||
if not include_children:
|
||||
return tag_ids
|
||||
outp = []
|
||||
for tag_id in tag_ids:
|
||||
outp.extend(list(session.scalars(CHILDREN_QUERY, {"tag_id": tag_id})))
|
||||
return outp
|
||||
|
||||
def __entry_has_all_tags(self, tag_ids: list[int]) -> BinaryExpression[bool]:
|
||||
"""Returns Binary Expression that is true if the Entry has all provided tag ids."""
|
||||
|
||||
@@ -79,11 +79,11 @@ TAG_COLORS: dict[TagColor, dict[ColorType, Any]] = {
|
||||
ColorType.DARK_ACCENT: "#6c2e3b",
|
||||
},
|
||||
TagColor.PINK: {
|
||||
ColorType.PRIMARY: "#ff99c4",
|
||||
ColorType.PRIMARY: "#F96BB1",
|
||||
ColorType.TEXT: ColorType.DARK_ACCENT,
|
||||
ColorType.BORDER: "#ffaad0",
|
||||
ColorType.LIGHT_ACCENT: "#ffcbe7",
|
||||
ColorType.DARK_ACCENT: "#6c2e3b",
|
||||
ColorType.BORDER: "#FA7EBC",
|
||||
ColorType.LIGHT_ACCENT: "#FDB6DC",
|
||||
ColorType.DARK_ACCENT: "#5B2135",
|
||||
},
|
||||
TagColor.MAGENTA: {
|
||||
ColorType.PRIMARY: "#f6466f",
|
||||
@@ -95,8 +95,7 @@ TAG_COLORS: dict[TagColor, dict[ColorType, Any]] = {
|
||||
TagColor.RED: {
|
||||
ColorType.PRIMARY: "#e22c3c",
|
||||
ColorType.TEXT: ColorType.DARK_ACCENT,
|
||||
ColorType.BORDER: "#b21f2d",
|
||||
# ColorType.BORDER: '#e54252',
|
||||
ColorType.BORDER: "#e54252",
|
||||
ColorType.LIGHT_ACCENT: "#f39caa",
|
||||
ColorType.DARK_ACCENT: "#440d12",
|
||||
},
|
||||
@@ -131,8 +130,7 @@ TAG_COLORS: dict[TagColor, dict[ColorType, Any]] = {
|
||||
TagColor.YELLOW: {
|
||||
ColorType.PRIMARY: "#ffd63d",
|
||||
ColorType.TEXT: ColorType.DARK_ACCENT,
|
||||
# ColorType.BORDER: '#ffe071',
|
||||
ColorType.BORDER: "#e8af31",
|
||||
ColorType.BORDER: "#ffe071",
|
||||
ColorType.LIGHT_ACCENT: "#fff3c4",
|
||||
ColorType.DARK_ACCENT: "#754312",
|
||||
},
|
||||
|
||||
@@ -25,6 +25,8 @@ from PySide6.QtWidgets import (QComboBox, QFrame, QGridLayout,
|
||||
from src.qt.pagination import Pagination
|
||||
from src.qt.widgets.landing import LandingWidget
|
||||
|
||||
from src.qt.translations import Translations
|
||||
|
||||
# Only import for type checking/autocompletion, will not be imported at runtime.
|
||||
if typing.TYPE_CHECKING:
|
||||
from src.qt.ts_qt import QtDriver
|
||||
@@ -77,6 +79,8 @@ class Ui_MainWindow(QMainWindow):
|
||||
# Thumbnail Size placeholder
|
||||
self.thumb_size_combobox = QComboBox(self.centralwidget)
|
||||
self.thumb_size_combobox.setObjectName(u"thumbSizeComboBox")
|
||||
Translations.translate_with_setter(self.thumb_size_combobox.setPlaceholderText, "home.thumbnail_size")
|
||||
self.thumb_size_combobox.setCurrentText("")
|
||||
sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
@@ -128,7 +132,7 @@ class Ui_MainWindow(QMainWindow):
|
||||
self.horizontalLayout_2 = QHBoxLayout()
|
||||
self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
|
||||
self.horizontalLayout_2.setSizeConstraint(QLayout.SetMinimumSize)
|
||||
self.backButton = QPushButton(self.centralwidget)
|
||||
self.backButton = QPushButton("<", self.centralwidget)
|
||||
self.backButton.setObjectName(u"backButton")
|
||||
self.backButton.setMinimumSize(QSize(0, 32))
|
||||
self.backButton.setMaximumSize(QSize(32, 16777215))
|
||||
@@ -139,7 +143,7 @@ class Ui_MainWindow(QMainWindow):
|
||||
|
||||
self.horizontalLayout_2.addWidget(self.backButton)
|
||||
|
||||
self.forwardButton = QPushButton(self.centralwidget)
|
||||
self.forwardButton = QPushButton(">", self.centralwidget)
|
||||
self.forwardButton.setObjectName(u"forwardButton")
|
||||
self.forwardButton.setMinimumSize(QSize(0, 32))
|
||||
self.forwardButton.setMaximumSize(QSize(32, 16777215))
|
||||
@@ -152,6 +156,7 @@ class Ui_MainWindow(QMainWindow):
|
||||
self.horizontalLayout_2.addWidget(self.forwardButton)
|
||||
|
||||
self.searchField = QLineEdit(self.centralwidget)
|
||||
Translations.translate_with_setter(self.searchField.setPlaceholderText, "home.search_entries")
|
||||
self.searchField.setObjectName(u"searchField")
|
||||
self.searchField.setMinimumSize(QSize(0, 32))
|
||||
font2 = QFont()
|
||||
@@ -167,6 +172,7 @@ class Ui_MainWindow(QMainWindow):
|
||||
self.horizontalLayout_2.addWidget(self.searchField)
|
||||
|
||||
self.searchButton = QPushButton(self.centralwidget)
|
||||
Translations.translate_qobject(self.searchButton, "home.search")
|
||||
self.searchButton.setObjectName(u"searchButton")
|
||||
self.searchButton.setMinimumSize(QSize(0, 32))
|
||||
self.searchButton.setFont(font2)
|
||||
@@ -186,33 +192,9 @@ class Ui_MainWindow(QMainWindow):
|
||||
self.statusbar.setSizePolicy(sizePolicy1)
|
||||
MainWindow.setStatusBar(self.statusbar)
|
||||
|
||||
self.retranslateUi(MainWindow)
|
||||
|
||||
QMetaObject.connectSlotsByName(MainWindow)
|
||||
# setupUi
|
||||
|
||||
def retranslateUi(self, MainWindow):
|
||||
MainWindow.setWindowTitle(QCoreApplication.translate(
|
||||
"MainWindow", u"MainWindow", None))
|
||||
# Navigation buttons
|
||||
self.backButton.setText(
|
||||
QCoreApplication.translate("MainWindow", u"<", None))
|
||||
self.forwardButton.setText(
|
||||
QCoreApplication.translate("MainWindow", u">", None))
|
||||
|
||||
# Search field
|
||||
self.searchField.setPlaceholderText(
|
||||
QCoreApplication.translate("MainWindow", u"Search Entries", None))
|
||||
self.searchButton.setText(
|
||||
QCoreApplication.translate("MainWindow", u"Search", None))
|
||||
|
||||
self.thumb_size_combobox.setCurrentText("")
|
||||
|
||||
# Thumbnail size selector
|
||||
self.thumb_size_combobox.setPlaceholderText(
|
||||
QCoreApplication.translate("MainWindow", u"Thumbnail Size", None))
|
||||
# retranslateUi
|
||||
|
||||
def moveEvent(self, event) -> None:
|
||||
# time.sleep(0.02) # sleep for 20ms
|
||||
pass
|
||||
|
||||
@@ -14,6 +14,7 @@ from PySide6.QtWidgets import (
|
||||
QWidget,
|
||||
)
|
||||
from src.core.library import Library
|
||||
from src.qt.translations import Translations
|
||||
|
||||
|
||||
class AddFieldModal(QWidget):
|
||||
@@ -26,7 +27,7 @@ class AddFieldModal(QWidget):
|
||||
super().__init__()
|
||||
self.is_connected = False
|
||||
self.lib = library
|
||||
self.setWindowTitle("Add Field")
|
||||
Translations.translate_with_setter(self.setWindowTitle, "library.field.add")
|
||||
self.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||
self.setMinimumSize(400, 300)
|
||||
self.root_layout = QVBoxLayout(self)
|
||||
@@ -40,7 +41,7 @@ class AddFieldModal(QWidget):
|
||||
# 'text-align:center;'
|
||||
"font-weight:bold;" "font-size:14px;" "padding-top: 6px" ""
|
||||
)
|
||||
self.title_widget.setText("Add Field")
|
||||
Translations.translate_qobject(self.title_widget, "library.field.add")
|
||||
self.title_widget.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
self.list_widget = QListWidget()
|
||||
@@ -54,13 +55,13 @@ class AddFieldModal(QWidget):
|
||||
# self.cancel_button.setText('Cancel')
|
||||
|
||||
self.cancel_button = QPushButton()
|
||||
self.cancel_button.setText("Cancel")
|
||||
Translations.translate_qobject(self.cancel_button, "generic.cancel")
|
||||
self.cancel_button.clicked.connect(self.hide)
|
||||
# self.cancel_button.clicked.connect(widget.reset)
|
||||
self.button_layout.addWidget(self.cancel_button)
|
||||
|
||||
self.save_button = QPushButton()
|
||||
self.save_button.setText("Add")
|
||||
Translations.translate_qobject(self.save_button, "generic.add")
|
||||
# self.save_button.setAutoDefault(True)
|
||||
self.save_button.setDefault(True)
|
||||
self.save_button.clicked.connect(self.hide)
|
||||
|
||||
@@ -24,6 +24,7 @@ from src.core.library import Library, Tag
|
||||
from src.core.library.alchemy.enums import TagColor
|
||||
from src.core.palette import ColorType, UiColor, get_tag_color, get_ui_color
|
||||
from src.qt.modals.tag_search import TagSearchPanel
|
||||
from src.qt.translations import Translations
|
||||
from src.qt.widgets.panel import PanelModal, PanelWidget
|
||||
from src.qt.widgets.tag import TagWidget
|
||||
|
||||
@@ -69,12 +70,14 @@ class BuildTagPanel(PanelWidget):
|
||||
self.name_layout.setSpacing(0)
|
||||
self.name_layout.setAlignment(Qt.AlignmentFlag.AlignLeft)
|
||||
self.name_title = QLabel()
|
||||
self.name_title.setText("Name")
|
||||
Translations.translate_qobject(self.name_title, "tag.name")
|
||||
self.name_layout.addWidget(self.name_title)
|
||||
self.name_field = QLineEdit()
|
||||
self.name_field.setFixedHeight(24)
|
||||
self.name_field.textChanged.connect(self.on_name_changed)
|
||||
self.name_field.setPlaceholderText("Tag Name (Required)")
|
||||
Translations.translate_with_setter(
|
||||
self.name_field.setPlaceholderText, "tag.tag_name_required"
|
||||
)
|
||||
self.name_layout.addWidget(self.name_field)
|
||||
|
||||
# Shorthand ------------------------------------------------------------
|
||||
@@ -85,7 +88,7 @@ class BuildTagPanel(PanelWidget):
|
||||
self.shorthand_layout.setSpacing(0)
|
||||
self.shorthand_layout.setAlignment(Qt.AlignmentFlag.AlignLeft)
|
||||
self.shorthand_title = QLabel()
|
||||
self.shorthand_title.setText("Shorthand")
|
||||
Translations.translate_qobject(self.shorthand_title, "tag.shorthand")
|
||||
self.shorthand_layout.addWidget(self.shorthand_title)
|
||||
self.shorthand_field = QLineEdit()
|
||||
self.shorthand_layout.addWidget(self.shorthand_field)
|
||||
@@ -98,7 +101,7 @@ class BuildTagPanel(PanelWidget):
|
||||
self.aliases_layout.setSpacing(0)
|
||||
self.aliases_layout.setAlignment(Qt.AlignmentFlag.AlignLeft)
|
||||
self.aliases_title = QLabel()
|
||||
self.aliases_title.setText("Aliases")
|
||||
Translations.translate_qobject(self.aliases_title, "tag.aliases")
|
||||
self.aliases_layout.addWidget(self.aliases_title)
|
||||
|
||||
self.aliases_table = QTableWidget(0, 2)
|
||||
@@ -124,7 +127,7 @@ class BuildTagPanel(PanelWidget):
|
||||
self.subtags_layout.setAlignment(Qt.AlignmentFlag.AlignLeft)
|
||||
|
||||
self.subtags_title = QLabel()
|
||||
self.subtags_title.setText("Parent Tags")
|
||||
Translations.translate_qobject(self.subtags_title, "tag.parent_tags")
|
||||
self.subtags_layout.addWidget(self.subtags_title)
|
||||
|
||||
self.scroll_contents = QWidget()
|
||||
@@ -154,7 +157,9 @@ class BuildTagPanel(PanelWidget):
|
||||
|
||||
tsp = TagSearchPanel(self.lib, exclude_ids)
|
||||
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)
|
||||
Translations.translate_with_setter(self.add_tag_modal.setTitle, "tag.parent_tags.add")
|
||||
Translations.translate_with_setter(self.add_tag_modal.setWindowTitle, "tag.parent_tags.add")
|
||||
self.subtags_add_button.clicked.connect(self.add_tag_modal.show)
|
||||
|
||||
# Shorthand ------------------------------------------------------------
|
||||
@@ -165,7 +170,7 @@ class BuildTagPanel(PanelWidget):
|
||||
self.color_layout.setSpacing(0)
|
||||
self.color_layout.setAlignment(Qt.AlignmentFlag.AlignLeft)
|
||||
self.color_title = QLabel()
|
||||
self.color_title.setText("Color")
|
||||
Translations.translate_qobject(self.color_title, "tag.color")
|
||||
self.color_layout.addWidget(self.color_title)
|
||||
self.color_field = QComboBox()
|
||||
self.color_field.setEditable(False)
|
||||
@@ -197,13 +202,13 @@ class BuildTagPanel(PanelWidget):
|
||||
self.root_layout.addWidget(self.subtags_widget)
|
||||
self.root_layout.addWidget(self.color_widget)
|
||||
|
||||
self.subtag_ids: list[int] = []
|
||||
self.subtag_ids: set[int] = set()
|
||||
self.alias_ids: list[int] = []
|
||||
self.alias_names: list[str] = []
|
||||
self.new_alias_names: dict = {}
|
||||
self.new_item_id = sys.maxsize
|
||||
|
||||
self.set_tag(tag or Tag(name="New Tag"))
|
||||
self.set_tag(tag or Tag(name=Translations["tag.new"]))
|
||||
|
||||
def backspace(self):
|
||||
focused_widget = QApplication.focusWidget()
|
||||
@@ -237,7 +242,7 @@ class BuildTagPanel(PanelWidget):
|
||||
|
||||
def add_subtag_callback(self, tag_id: int):
|
||||
logger.info("add_subtag_callback", tag_id=tag_id)
|
||||
self.subtag_ids.append(tag_id)
|
||||
self.subtag_ids.add(tag_id)
|
||||
self.set_subtags()
|
||||
|
||||
def remove_subtag_callback(self, tag_id: int):
|
||||
@@ -369,7 +374,7 @@ class BuildTagPanel(PanelWidget):
|
||||
self._set_aliases()
|
||||
|
||||
for subtag in tag.subtag_ids:
|
||||
self.subtag_ids.append(subtag)
|
||||
self.subtag_ids.add(subtag)
|
||||
|
||||
self.set_subtags()
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ from PySide6.QtWidgets import (
|
||||
QWidget,
|
||||
)
|
||||
from src.core.utils.missing_files import MissingRegistry
|
||||
from src.qt.translations import Translations
|
||||
from src.qt.widgets.progress import ProgressWidget
|
||||
|
||||
# Only import for type checking/autocompletion, will not be imported at runtime.
|
||||
@@ -29,7 +30,7 @@ class DeleteUnlinkedEntriesModal(QWidget):
|
||||
super().__init__()
|
||||
self.driver = driver
|
||||
self.tracker = tracker
|
||||
self.setWindowTitle("Delete Unlinked Entries")
|
||||
Translations.translate_with_setter(self.setWindowTitle, "entries.unlinked.delete")
|
||||
self.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||
self.setMinimumSize(500, 400)
|
||||
self.root_layout = QVBoxLayout(self)
|
||||
@@ -38,9 +39,11 @@ class DeleteUnlinkedEntriesModal(QWidget):
|
||||
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?
|
||||
""")
|
||||
Translations.translate_qobject(
|
||||
self.desc_widget,
|
||||
"entries.unlinked.delete.confirm",
|
||||
count=self.tracker.missing_files_count,
|
||||
)
|
||||
self.desc_widget.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
self.list_view = QListView()
|
||||
@@ -53,13 +56,13 @@ class DeleteUnlinkedEntriesModal(QWidget):
|
||||
self.button_layout.addStretch(1)
|
||||
|
||||
self.cancel_button = QPushButton()
|
||||
self.cancel_button.setText("&Cancel")
|
||||
Translations.translate_qobject(self.cancel_button, "generic.cancel_alt")
|
||||
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")
|
||||
Translations.translate_qobject(self.delete_button, "generic.delete_alt")
|
||||
self.delete_button.clicked.connect(self.hide)
|
||||
self.delete_button.clicked.connect(lambda: self.delete_entries())
|
||||
self.button_layout.addWidget(self.delete_button)
|
||||
@@ -69,9 +72,11 @@ class DeleteUnlinkedEntriesModal(QWidget):
|
||||
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(
|
||||
Translations.translate_formatted(
|
||||
"entries.unlinked.delete.confirm", count=self.tracker.missing_files_count
|
||||
)
|
||||
)
|
||||
|
||||
self.model.clear()
|
||||
for i in self.tracker.missing_files:
|
||||
@@ -81,14 +86,17 @@ class DeleteUnlinkedEntriesModal(QWidget):
|
||||
|
||||
def delete_entries(self):
|
||||
def displayed_text(x):
|
||||
return f"Deleting {x}/{self.tracker.missing_files_count} Unlinked Entries"
|
||||
return Translations.translate_formatted(
|
||||
"entries.unlinked.delete.deleting_count",
|
||||
idx=x,
|
||||
count=self.tracker.missing_files_count,
|
||||
)
|
||||
|
||||
pw = ProgressWidget(
|
||||
window_title="Deleting Entries",
|
||||
label_text="",
|
||||
cancel_button_text=None,
|
||||
minimum=0,
|
||||
maximum=self.tracker.missing_files_count,
|
||||
)
|
||||
Translations.translate_with_setter(pw.setWindowTitle, "entries.unlinked.delete.deleting")
|
||||
|
||||
pw.from_iterable_function(self.tracker.execute_deletion, displayed_text, self.done.emit)
|
||||
|
||||
@@ -17,6 +17,7 @@ from PySide6.QtWidgets import (
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
from src.qt.translations import Translations
|
||||
from src.qt.widgets.progress import ProgressWidget
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -41,7 +42,7 @@ class DropImportModal(QWidget):
|
||||
self.driver: QtDriver = driver
|
||||
|
||||
# Widget ======================
|
||||
self.setWindowTitle("Conflicting File(s)")
|
||||
Translations.translate_with_setter(self.setWindowTitle, "drop_import.title")
|
||||
self.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||
self.setMinimumSize(500, 400)
|
||||
self.root_layout = QVBoxLayout(self)
|
||||
@@ -50,7 +51,7 @@ class DropImportModal(QWidget):
|
||||
self.desc_widget = QLabel()
|
||||
self.desc_widget.setObjectName("descriptionLabel")
|
||||
self.desc_widget.setWordWrap(True)
|
||||
self.desc_widget.setText("The following files have filenames already exist in the library")
|
||||
self.desc_widget.setText(Translations["drop_import.description"])
|
||||
self.desc_widget.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
# Duplicate File List ========
|
||||
@@ -65,25 +66,25 @@ class DropImportModal(QWidget):
|
||||
self.button_layout.addStretch(1)
|
||||
|
||||
self.skip_button = QPushButton()
|
||||
self.skip_button.setText("&Skip")
|
||||
Translations.translate_qobject(self.skip_button, "generic.skip_alt")
|
||||
self.skip_button.setDefault(True)
|
||||
self.skip_button.clicked.connect(lambda: self.begin_transfer(DuplicateChoice.SKIP))
|
||||
self.button_layout.addWidget(self.skip_button)
|
||||
|
||||
self.overwrite_button = QPushButton()
|
||||
self.overwrite_button.setText("&Overwrite")
|
||||
Translations.translate_qobject(self.overwrite_button, "generic.overwrite_alt")
|
||||
self.overwrite_button.clicked.connect(
|
||||
lambda: self.begin_transfer(DuplicateChoice.OVERWRITE)
|
||||
)
|
||||
self.button_layout.addWidget(self.overwrite_button)
|
||||
|
||||
self.rename_button = QPushButton()
|
||||
self.rename_button.setText("&Rename")
|
||||
Translations.translate_qobject(self.rename_button, "generic.rename_alt")
|
||||
self.rename_button.clicked.connect(lambda: self.begin_transfer(DuplicateChoice.RENAME))
|
||||
self.button_layout.addWidget(self.rename_button)
|
||||
|
||||
self.cancel_button = QPushButton()
|
||||
self.cancel_button.setText("&Cancel")
|
||||
Translations.translate_qobject(self.cancel_button, "generic.cancel_alt")
|
||||
self.cancel_button.clicked.connect(lambda: self.begin_transfer(DuplicateChoice.CANCEL))
|
||||
self.button_layout.addWidget(self.cancel_button)
|
||||
|
||||
@@ -137,7 +138,11 @@ class DropImportModal(QWidget):
|
||||
def ask_duplicates_choice(self):
|
||||
"""Display the message widgeth with a list of the duplicated files."""
|
||||
self.desc_widget.setText(
|
||||
f"The following {len(self.duplicate_files)} file(s) have filenames already exist in the library." # noqa: E501
|
||||
Translations["drop_import.duplicates_choice.singular"]
|
||||
if len(self.duplicate_files) == 1
|
||||
else Translations.translate_formatted(
|
||||
"drop_import.duplicates_choice.plural", count=len(self.duplicate_files)
|
||||
)
|
||||
)
|
||||
|
||||
self.model.clear()
|
||||
@@ -158,21 +163,21 @@ class DropImportModal(QWidget):
|
||||
return
|
||||
|
||||
def displayed_text(x):
|
||||
text = (
|
||||
f"Importing New Files...\n{x[0] + 1} File{'s' if x[0] + 1 != 1 else ''} Imported."
|
||||
return Translations.translate_formatted(
|
||||
"drop_import.progress.label.singular"
|
||||
if x[0] + 1 == 1
|
||||
else "drop_import.progress.label.plural",
|
||||
count=x[0] + 1,
|
||||
suffix=f" {x[1]} {self.choice.value}" if self.choice else "",
|
||||
)
|
||||
if self.choice:
|
||||
text += f" {x[1]} {self.choice.value}"
|
||||
|
||||
return text
|
||||
|
||||
pw = ProgressWidget(
|
||||
window_title="Import Files",
|
||||
label_text="Importing New Files...",
|
||||
cancel_button_text=None,
|
||||
minimum=0,
|
||||
maximum=len(self.files),
|
||||
)
|
||||
Translations.translate_with_setter(pw.setWindowTitle, "drop_import.progress.window_title")
|
||||
Translations.translate_with_setter(pw.update_label, "drop_import.progress.label.initial")
|
||||
|
||||
pw.from_iterable_function(
|
||||
self.copy_files,
|
||||
|
||||
@@ -18,6 +18,7 @@ from PySide6.QtWidgets import (
|
||||
)
|
||||
from src.core.enums import LibraryPrefs
|
||||
from src.core.library import Library
|
||||
from src.qt.translations import Translations
|
||||
from src.qt.widgets.panel import PanelWidget
|
||||
|
||||
|
||||
@@ -35,7 +36,7 @@ class FileExtensionModal(PanelWidget):
|
||||
super().__init__()
|
||||
# Initialize Modal =====================================================
|
||||
self.lib = library
|
||||
self.setWindowTitle("File Extensions")
|
||||
Translations.translate_with_setter(self.setWindowTitle, "ignore_list.title")
|
||||
self.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||
self.setMinimumSize(240, 400)
|
||||
self.root_layout = QVBoxLayout(self)
|
||||
@@ -50,7 +51,7 @@ class FileExtensionModal(PanelWidget):
|
||||
|
||||
# Create "Add Button" Widget -------------------------------------------
|
||||
self.add_button = QPushButton()
|
||||
self.add_button.setText("&Add Extension")
|
||||
Translations.translate_qobject(self.add_button, "ignore_list.add_extension")
|
||||
self.add_button.clicked.connect(self.add_item)
|
||||
self.add_button.setDefault(True)
|
||||
self.add_button.setMinimumWidth(100)
|
||||
@@ -61,11 +62,17 @@ class FileExtensionModal(PanelWidget):
|
||||
self.mode_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.mode_layout.setSpacing(12)
|
||||
self.mode_label = QLabel()
|
||||
self.mode_label.setText("List Mode:")
|
||||
Translations.translate_qobject(self.mode_label, "ignore_list.mode.label")
|
||||
self.mode_combobox = QComboBox()
|
||||
self.mode_combobox.setEditable(False)
|
||||
self.mode_combobox.addItem("Include")
|
||||
self.mode_combobox.addItem("Exclude")
|
||||
self.mode_combobox.addItem("")
|
||||
self.mode_combobox.addItem("")
|
||||
Translations.translate_with_setter(
|
||||
lambda text: self.mode_combobox.setItemText(0, text), "ignore_list.mode.include"
|
||||
)
|
||||
Translations.translate_with_setter(
|
||||
lambda text: self.mode_combobox.setItemText(1, text), "ignore_list.mode.exclude"
|
||||
)
|
||||
|
||||
is_exclude_list = int(bool(self.lib.prefs(LibraryPrefs.IS_EXCLUDE_LIST)))
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ from PySide6.QtWidgets import (
|
||||
from src.core.library import Library
|
||||
from src.core.utils.dupe_files import DupeRegistry
|
||||
from src.qt.modals.mirror_entities import MirrorEntriesModal
|
||||
from src.qt.translations import Translations
|
||||
|
||||
# Only import for type checking/autocompletion, will not be imported at runtime.
|
||||
if typing.TYPE_CHECKING:
|
||||
@@ -30,7 +31,7 @@ class FixDupeFilesModal(QWidget):
|
||||
self.driver = driver
|
||||
self.count = -1
|
||||
self.filename = ""
|
||||
self.setWindowTitle("Fix Duplicate Files")
|
||||
Translations.translate_with_setter(self.setWindowTitle, "file.duplicates.fix")
|
||||
self.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||
self.setMinimumSize(400, 300)
|
||||
self.root_layout = QVBoxLayout(self)
|
||||
@@ -42,9 +43,7 @@ class FixDupeFilesModal(QWidget):
|
||||
self.desc_widget.setObjectName("descriptionLabel")
|
||||
self.desc_widget.setWordWrap(True)
|
||||
self.desc_widget.setStyleSheet("text-align:left;")
|
||||
self.desc_widget.setText(
|
||||
"TagStudio supports importing DupeGuru results to manage duplicate files."
|
||||
)
|
||||
Translations.translate_qobject(self.desc_widget, "file.duplicates.description")
|
||||
self.desc_widget.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
self.dupe_count = QLabel()
|
||||
@@ -54,34 +53,25 @@ class FixDupeFilesModal(QWidget):
|
||||
|
||||
self.file_label = QLabel()
|
||||
self.file_label.setObjectName("fileLabel")
|
||||
self.file_label.setText("No DupeGuru File Selected")
|
||||
Translations.translate_qobject(self.file_label, "file.duplicates.dupeguru.no_file")
|
||||
|
||||
self.open_button = QPushButton()
|
||||
self.open_button.setText("&Load DupeGuru File")
|
||||
Translations.translate_qobject(self.open_button, "file.duplicates.dupeguru.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")
|
||||
Translations.translate_qobject(self.mirror_button, "file.duplicates.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."
|
||||
)
|
||||
Translations.translate_qobject(self.mirror_desc, "file.duplicates.mirror.description")
|
||||
|
||||
self.advice_label = QLabel()
|
||||
self.advice_label.setWordWrap(True)
|
||||
# fmt: off
|
||||
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."
|
||||
)
|
||||
# fmt: on
|
||||
Translations.translate_qobject(self.advice_label, "file.duplicates.dupeguru.advice")
|
||||
|
||||
self.button_container = QWidget()
|
||||
self.button_layout = QHBoxLayout(self.button_container)
|
||||
@@ -89,7 +79,7 @@ class FixDupeFilesModal(QWidget):
|
||||
self.button_layout.addStretch(1)
|
||||
|
||||
self.done_button = QPushButton()
|
||||
self.done_button.setText("&Done")
|
||||
Translations.translate_qobject(self.done_button, "generic.done_alt")
|
||||
self.done_button.setDefault(True)
|
||||
self.done_button.clicked.connect(self.hide)
|
||||
self.button_layout.addWidget(self.done_button)
|
||||
@@ -108,9 +98,11 @@ class FixDupeFilesModal(QWidget):
|
||||
self.set_dupe_count(-1)
|
||||
|
||||
def select_file(self):
|
||||
qfd = QFileDialog(self, "Open DupeGuru Results File", str(self.lib.library_dir))
|
||||
qfd = QFileDialog(
|
||||
self, Translations["file.duplicates.dupeguru.open_file"], str(self.lib.library_dir)
|
||||
)
|
||||
qfd.setFileMode(QFileDialog.FileMode.ExistingFile)
|
||||
qfd.setNameFilter("DupeGuru Files (*.dupeguru)")
|
||||
qfd.setNameFilter(Translations["file.duplicates.dupeguru.file_extension"])
|
||||
if qfd.exec_():
|
||||
filename = qfd.selectedFiles()
|
||||
if filename:
|
||||
@@ -120,7 +112,7 @@ class FixDupeFilesModal(QWidget):
|
||||
if filename:
|
||||
self.file_label.setText(filename)
|
||||
else:
|
||||
self.file_label.setText("No DupeGuru File Selected")
|
||||
self.file_label.setText(Translations["file.duplicates.dupeguru.no_file"])
|
||||
self.filename = filename
|
||||
self.refresh_dupes()
|
||||
self.mirror_modal.refresh_list()
|
||||
@@ -132,10 +124,14 @@ class FixDupeFilesModal(QWidget):
|
||||
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(Translations["file.duplicates.matches_uninitialized"])
|
||||
elif count == 0:
|
||||
self.mirror_button.setDisabled(True)
|
||||
self.dupe_count.setText(f"Duplicate File Matches: {count}")
|
||||
self.dupe_count.setText(
|
||||
Translations.translate_formatted("file.duplicates.matches", count=count)
|
||||
)
|
||||
else:
|
||||
self.mirror_button.setDisabled(False)
|
||||
self.dupe_count.setText(f"Duplicate File Matches: {count}")
|
||||
self.dupe_count.setText(
|
||||
Translations.translate_formatted("file.duplicates.matches", count=count)
|
||||
)
|
||||
|
||||
@@ -12,6 +12,7 @@ from src.core.utils.missing_files import MissingRegistry
|
||||
from src.qt.modals.delete_unlinked import DeleteUnlinkedEntriesModal
|
||||
from src.qt.modals.merge_dupe_entries import MergeDuplicateEntries
|
||||
from src.qt.modals.relink_unlinked import RelinkUnlinkedEntries
|
||||
from src.qt.translations import Translations
|
||||
from src.qt.widgets.progress import ProgressWidget
|
||||
|
||||
# Only import for type checking/autocompletion, will not be imported at runtime.
|
||||
@@ -29,7 +30,7 @@ class FixUnlinkedEntriesModal(QWidget):
|
||||
|
||||
self.missing_count = -1
|
||||
self.dupe_count = -1
|
||||
self.setWindowTitle("Fix Unlinked Entries")
|
||||
Translations.translate_with_setter(self.setWindowTitle, "entries.unlinked.title")
|
||||
self.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||
self.setMinimumSize(400, 300)
|
||||
self.root_layout = QVBoxLayout(self)
|
||||
@@ -39,13 +40,7 @@ class FixUnlinkedEntriesModal(QWidget):
|
||||
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.\n\n"
|
||||
"Unlinked entries may be automatically relinked via searching your directories, "
|
||||
"manually relinked by the user, or deleted if desired."
|
||||
)
|
||||
Translations.translate_qobject(self.unlinked_desc_widget, "entries.unlinked.description")
|
||||
|
||||
self.missing_count_label = QLabel()
|
||||
self.missing_count_label.setObjectName("missingCountLabel")
|
||||
@@ -58,14 +53,14 @@ class FixUnlinkedEntriesModal(QWidget):
|
||||
self.dupe_count_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
self.refresh_unlinked_button = QPushButton()
|
||||
self.refresh_unlinked_button.setText("&Refresh All")
|
||||
Translations.translate_qobject(self.refresh_unlinked_button, "entries.unlinked.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")
|
||||
Translations.translate_qobject(self.search_button, "entries.unlinked.search_and_relink")
|
||||
self.relink_class.done.connect(
|
||||
# refresh the grid
|
||||
lambda: (
|
||||
@@ -76,7 +71,7 @@ class FixUnlinkedEntriesModal(QWidget):
|
||||
self.search_button.clicked.connect(self.relink_class.repair_entries)
|
||||
|
||||
self.manual_button = QPushButton()
|
||||
self.manual_button.setText("&Manual Relink")
|
||||
Translations.translate_qobject(self.manual_button, "entries.unlinked.relink.manual")
|
||||
self.manual_button.setHidden(True)
|
||||
|
||||
self.delete_button = QPushButton()
|
||||
@@ -88,7 +83,7 @@ class FixUnlinkedEntriesModal(QWidget):
|
||||
self.driver.filter_items(),
|
||||
)
|
||||
)
|
||||
self.delete_button.setText("De&lete Unlinked Entries")
|
||||
Translations.translate_qobject(self.delete_button, "entries.unlinked.delete_alt")
|
||||
self.delete_button.clicked.connect(self.delete_modal.show)
|
||||
|
||||
self.button_container = QWidget()
|
||||
@@ -97,7 +92,7 @@ class FixUnlinkedEntriesModal(QWidget):
|
||||
self.button_layout.addStretch(1)
|
||||
|
||||
self.done_button = QPushButton()
|
||||
self.done_button.setText("&Done")
|
||||
Translations.translate_qobject(self.done_button, "generic.done_alt")
|
||||
self.done_button.setDefault(True)
|
||||
self.done_button.clicked.connect(self.hide)
|
||||
self.button_layout.addWidget(self.done_button)
|
||||
@@ -116,12 +111,12 @@ class FixUnlinkedEntriesModal(QWidget):
|
||||
|
||||
def refresh_missing_files(self):
|
||||
pw = ProgressWidget(
|
||||
window_title="Scanning Library",
|
||||
label_text="Scanning Library for Unlinked Entries...",
|
||||
cancel_button_text=None,
|
||||
minimum=0,
|
||||
maximum=self.lib.entries_count,
|
||||
)
|
||||
Translations.translate_with_setter(pw.setWindowTitle, "library.scan_library.title")
|
||||
Translations.translate_with_setter(pw.update_label, "entries.unlinked.scanning")
|
||||
|
||||
pw.from_iterable_function(
|
||||
self.tracker.refresh_missing_files,
|
||||
@@ -139,9 +134,13 @@ class FixUnlinkedEntriesModal(QWidget):
|
||||
if self.missing_count < 0:
|
||||
self.search_button.setDisabled(True)
|
||||
self.delete_button.setDisabled(True)
|
||||
self.missing_count_label.setText("Unlinked Entries: N/A")
|
||||
self.missing_count_label.setText(Translations["entries.unlinked.missing_count.none"])
|
||||
else:
|
||||
# disable buttons if there are no files to fix
|
||||
self.search_button.setDisabled(self.missing_count == 0)
|
||||
self.delete_button.setDisabled(self.missing_count == 0)
|
||||
self.missing_count_label.setText(f"Unlinked Entries: {self.missing_count}")
|
||||
self.missing_count_label.setText(
|
||||
Translations.translate_formatted(
|
||||
"entries.unlinked.missing_count.some", count=self.missing_count
|
||||
)
|
||||
)
|
||||
|
||||
@@ -23,6 +23,7 @@ from src.core.library import Library, Tag
|
||||
from src.core.library.alchemy.fields import _FieldID
|
||||
from src.core.palette import ColorType, get_tag_color
|
||||
from src.qt.flowlayout import FlowLayout
|
||||
from src.qt.translations import Translations
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from src.qt.ts_qt import QtDriver
|
||||
@@ -164,7 +165,7 @@ class FoldersToTagsModal(QWidget):
|
||||
self.count = -1
|
||||
self.filename = ""
|
||||
|
||||
self.setWindowTitle("Create Tags From Folders")
|
||||
Translations.translate_with_setter(self.setWindowTitle, "folders_to_tags.title")
|
||||
self.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||
self.setMinimumSize(640, 640)
|
||||
self.root_layout = QVBoxLayout(self)
|
||||
@@ -174,7 +175,7 @@ class FoldersToTagsModal(QWidget):
|
||||
self.title_widget.setObjectName("title")
|
||||
self.title_widget.setWordWrap(True)
|
||||
self.title_widget.setStyleSheet("font-weight:bold;" "font-size:14px;" "padding-top: 6px")
|
||||
self.title_widget.setText("Create Tags From Folders")
|
||||
Translations.translate_qobject(self.title_widget, "folders_to_tags.title")
|
||||
self.title_widget.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
self.desc_widget = QLabel()
|
||||
@@ -190,10 +191,10 @@ class FoldersToTagsModal(QWidget):
|
||||
self.open_close_button_layout = QHBoxLayout(self.open_close_button_w)
|
||||
|
||||
self.open_all_button = QPushButton()
|
||||
self.open_all_button.setText("Open All")
|
||||
Translations.translate_qobject(self.open_all_button, "folders_to_tags.open_all")
|
||||
self.open_all_button.clicked.connect(lambda: self.set_all_branches(False))
|
||||
self.close_all_button = QPushButton()
|
||||
self.close_all_button.setText("Close All")
|
||||
Translations.translate_qobject(self.close_all_button, "folders_to_tags.close_all")
|
||||
self.close_all_button.clicked.connect(lambda: self.set_all_branches(True))
|
||||
|
||||
self.open_close_button_layout.addWidget(self.open_all_button)
|
||||
@@ -212,7 +213,7 @@ class FoldersToTagsModal(QWidget):
|
||||
self.scroll_area.setWidget(self.scroll_contents)
|
||||
|
||||
self.apply_button = QPushButton()
|
||||
self.apply_button.setText("&Apply")
|
||||
Translations.translate_qobject(self.apply_button, "generic.apply_alt")
|
||||
self.apply_button.setMinimumWidth(100)
|
||||
self.apply_button.clicked.connect(self.on_apply)
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import typing
|
||||
from PySide6.QtCore import QObject, Signal
|
||||
from src.core.library import Library
|
||||
from src.core.utils.dupe_files import DupeRegistry
|
||||
from src.qt.translations import Translations
|
||||
from src.qt.widgets.progress import ProgressWidget
|
||||
|
||||
# Only import for type checking/autocompletion, will not be imported at runtime.
|
||||
@@ -25,11 +26,11 @@ class MergeDuplicateEntries(QObject):
|
||||
|
||||
def merge_entries(self):
|
||||
pw = ProgressWidget(
|
||||
window_title="Merging Duplicate Entries",
|
||||
label_text="Merging Duplicate Entries...",
|
||||
cancel_button_text=None,
|
||||
minimum=0,
|
||||
maximum=self.tracker.groups_count,
|
||||
)
|
||||
Translations.translate_with_setter(pw.setWindowTitle, "entries.duplicate.merge.label")
|
||||
Translations.translate_with_setter(pw.update_label, "entries.duplicate.merge.label")
|
||||
|
||||
pw.from_iterable_function(self.tracker.merge_dupe_entries, None, self.done.emit)
|
||||
|
||||
@@ -17,6 +17,7 @@ from PySide6.QtWidgets import (
|
||||
QWidget,
|
||||
)
|
||||
from src.core.utils.dupe_files import DupeRegistry
|
||||
from src.qt.translations import Translations
|
||||
from src.qt.widgets.progress import ProgressWidget
|
||||
|
||||
# Only import for type checking/autocompletion, will not be imported at runtime.
|
||||
@@ -30,7 +31,7 @@ class MirrorEntriesModal(QWidget):
|
||||
def __init__(self, driver: "QtDriver", tracker: DupeRegistry):
|
||||
super().__init__()
|
||||
self.driver = driver
|
||||
self.setWindowTitle("Mirror Entries")
|
||||
Translations.translate_with_setter(self.setWindowTitle, "entries.mirror.window_title")
|
||||
self.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||
self.setMinimumSize(500, 400)
|
||||
self.root_layout = QVBoxLayout(self)
|
||||
@@ -40,10 +41,9 @@ class MirrorEntriesModal(QWidget):
|
||||
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 mirror the following {self.tracker.groups_count} Entries?
|
||||
""")
|
||||
Translations.translate_qobject(
|
||||
self.desc_widget, "entries.mirror.confirmation", count=self.tracker.groups_count
|
||||
)
|
||||
self.desc_widget.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
self.list_view = QListView()
|
||||
@@ -56,13 +56,13 @@ class MirrorEntriesModal(QWidget):
|
||||
self.button_layout.addStretch(1)
|
||||
|
||||
self.cancel_button = QPushButton()
|
||||
self.cancel_button.setText("&Cancel")
|
||||
Translations.translate_qobject(self.cancel_button, "generic.cancel_alt")
|
||||
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")
|
||||
Translations.translate_qobject(self.mirror_button, "entries.mirror")
|
||||
self.mirror_button.clicked.connect(self.hide)
|
||||
self.mirror_button.clicked.connect(self.mirror_entries)
|
||||
self.button_layout.addWidget(self.mirror_button)
|
||||
@@ -72,9 +72,11 @@ class MirrorEntriesModal(QWidget):
|
||||
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(
|
||||
Translations.translate_formatted(
|
||||
"entries.mirror.confirmation", count=self.tracker.groups_count
|
||||
)
|
||||
)
|
||||
|
||||
self.model.clear()
|
||||
for i in self.tracker.groups:
|
||||
@@ -82,15 +84,16 @@ class MirrorEntriesModal(QWidget):
|
||||
|
||||
def mirror_entries(self):
|
||||
def displayed_text(x):
|
||||
return f"Mirroring {x + 1}/{self.tracker.groups_count} Entries..."
|
||||
return Translations.translate_formatted(
|
||||
"entries.mirror.label", idx=x + 1, count=self.tracker.groups_count
|
||||
)
|
||||
|
||||
pw = ProgressWidget(
|
||||
window_title="Mirroring Entries",
|
||||
label_text="",
|
||||
cancel_button_text=None,
|
||||
minimum=0,
|
||||
maximum=self.tracker.groups_count,
|
||||
)
|
||||
Translations.translate_with_setter(pw.setWindowTitle, "entries.mirror.title")
|
||||
|
||||
pw.from_iterable_function(
|
||||
self.mirror_entries_runnable,
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
from PySide6.QtCore import QObject, Signal
|
||||
from src.core.utils.missing_files import MissingRegistry
|
||||
from src.qt.translations import Translations
|
||||
from src.qt.widgets.progress import ProgressWidget
|
||||
|
||||
|
||||
@@ -17,16 +18,19 @@ class RelinkUnlinkedEntries(QObject):
|
||||
|
||||
def repair_entries(self):
|
||||
def displayed_text(x):
|
||||
text = f"Attempting to Relink {x}/{self.tracker.missing_files_count} Entries. \n"
|
||||
text += f"{self.tracker.files_fixed_count} Successfully Relinked."
|
||||
return text
|
||||
return Translations.translate_formatted(
|
||||
"entries.unlinked.relink.attempting",
|
||||
idx=x,
|
||||
missing_count=self.tracker.missing_files_count,
|
||||
fixed_count=self.tracker.files_fixed_count,
|
||||
)
|
||||
|
||||
pw = ProgressWidget(
|
||||
window_title="Relinking Entries",
|
||||
label_text="",
|
||||
cancel_button_text=None,
|
||||
minimum=0,
|
||||
maximum=self.tracker.missing_files_count,
|
||||
)
|
||||
Translations.translate_with_setter(pw.setWindowTitle, "entries.unlinked.relink.title")
|
||||
|
||||
pw.from_iterable_function(self.tracker.fix_missing_files, displayed_text, self.done.emit)
|
||||
|
||||
@@ -18,6 +18,7 @@ from PySide6.QtWidgets import (
|
||||
from src.core.constants import RESERVED_TAG_END, RESERVED_TAG_START
|
||||
from src.core.library import Library, Tag
|
||||
from src.qt.modals.build_tag import BuildTagPanel
|
||||
from src.qt.translations import Translations
|
||||
from src.qt.widgets.panel import PanelModal, PanelWidget
|
||||
from src.qt.widgets.tag import TagWidget
|
||||
|
||||
@@ -44,7 +45,7 @@ class TagDatabasePanel(PanelWidget):
|
||||
self.search_field = QLineEdit()
|
||||
self.search_field.setObjectName("searchField")
|
||||
self.search_field.setMinimumSize(QSize(0, 32))
|
||||
self.search_field.setPlaceholderText("Search Tags")
|
||||
Translations.translate_with_setter(self.search_field.setPlaceholderText, "home.search_tags")
|
||||
self.search_field.textEdited.connect(lambda: self.update_tags(self.search_field.text()))
|
||||
self.search_field.returnPressed.connect(
|
||||
lambda checked=False: self.on_return(self.search_field.text())
|
||||
@@ -63,7 +64,7 @@ class TagDatabasePanel(PanelWidget):
|
||||
self.scroll_area.setWidget(self.scroll_contents)
|
||||
|
||||
self.create_tag_button = QPushButton()
|
||||
self.create_tag_button.setText("Create Tag")
|
||||
Translations.translate_qobject(self.create_tag_button, "tag.create")
|
||||
self.create_tag_button.clicked.connect(self.build_tag)
|
||||
|
||||
self.root_layout.addWidget(self.search_field)
|
||||
@@ -75,10 +76,10 @@ class TagDatabasePanel(PanelWidget):
|
||||
panel = BuildTagPanel(self.lib)
|
||||
self.modal = PanelModal(
|
||||
panel,
|
||||
"New Tag",
|
||||
"Add Tag",
|
||||
has_save=True,
|
||||
)
|
||||
Translations.translate_with_setter(self.modal.setTitle, "tag.new")
|
||||
Translations.translate_with_setter(self.modal.setWindowTitle, "tag.add")
|
||||
|
||||
self.modal.saved.connect(
|
||||
lambda: (
|
||||
@@ -134,8 +135,8 @@ class TagDatabasePanel(PanelWidget):
|
||||
return
|
||||
|
||||
message_box = QMessageBox()
|
||||
message_box.setWindowTitle("Remove Tag")
|
||||
message_box.setText(f'Are you sure you want to delete the tag "{tag.name}"?')
|
||||
Translations.translate_with_setter(message_box.setWindowTitle, "tag.remove")
|
||||
Translations.translate_qobject(message_box, "tag.confirm_delete", tag_name=tag.name)
|
||||
message_box.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel) # type: ignore
|
||||
message_box.setIcon(QMessageBox.Question) # type: ignore
|
||||
|
||||
@@ -156,10 +157,10 @@ class TagDatabasePanel(PanelWidget):
|
||||
self.edit_modal = PanelModal(
|
||||
build_tag_panel,
|
||||
tag.name,
|
||||
"Edit Tag",
|
||||
done_callback=(self.update_tags(self.search_field.text())),
|
||||
has_save=True,
|
||||
)
|
||||
Translations.translate_with_setter(self.edit_modal.setWindowTitle, "tag.edit")
|
||||
# TODO Check Warning: Expected type 'BuildTagPanel', got 'PanelWidget' instead
|
||||
self.edit_modal.saved.connect(lambda: self.edit_tag_callback(build_tag_panel))
|
||||
self.edit_modal.show()
|
||||
|
||||
@@ -19,6 +19,7 @@ from PySide6.QtWidgets import (
|
||||
)
|
||||
from src.core.library import Library
|
||||
from src.core.palette import ColorType, get_tag_color
|
||||
from src.qt.translations import Translations
|
||||
from src.qt.widgets.panel import PanelWidget
|
||||
from src.qt.widgets.tag import TagWidget
|
||||
|
||||
@@ -41,7 +42,7 @@ class TagSearchPanel(PanelWidget):
|
||||
self.search_field = QLineEdit()
|
||||
self.search_field.setObjectName("searchField")
|
||||
self.search_field.setMinimumSize(QSize(0, 32))
|
||||
self.search_field.setPlaceholderText("Search Tags")
|
||||
Translations.translate_with_setter(self.search_field.setPlaceholderText, "home.search_tags")
|
||||
self.search_field.textEdited.connect(lambda: self.update_tags(self.search_field.text()))
|
||||
self.search_field.returnPressed.connect(
|
||||
lambda checked=False: self.on_return(self.search_field.text())
|
||||
|
||||
@@ -6,11 +6,13 @@
|
||||
|
||||
import platform
|
||||
|
||||
from src.qt.translations import Translations
|
||||
|
||||
|
||||
class PlatformStrings:
|
||||
open_file_str: str = "Open in file explorer"
|
||||
open_file_str: str = Translations["file.open_location.generic"]
|
||||
|
||||
if platform.system() == "Windows":
|
||||
open_file_str = "Open in Explorer"
|
||||
open_file_str = Translations["file.open_location.windows"]
|
||||
elif platform.system() == "Darwin":
|
||||
open_file_str = "Reveal in Finder"
|
||||
open_file_str = Translations["file.open_location.mac"]
|
||||
|
||||
95
tagstudio/src/qt/translations.py
Normal file
95
tagstudio/src/qt/translations.py
Normal file
@@ -0,0 +1,95 @@
|
||||
from pathlib import Path
|
||||
from typing import Callable
|
||||
|
||||
import structlog
|
||||
import ujson
|
||||
from PySide6.QtCore import QObject, Signal
|
||||
from PySide6.QtGui import QAction
|
||||
from PySide6.QtWidgets import QLabel, QMenu, QMessageBox, QPushButton
|
||||
|
||||
from .helpers.qbutton_wrapper import QPushButtonWrapper
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
DEFAULT_TRANSLATION = "en"
|
||||
|
||||
|
||||
class TranslatedString(QObject):
|
||||
changed = Signal(str)
|
||||
|
||||
__default_value: str
|
||||
__value: str | None = None
|
||||
|
||||
def __init__(self, value: str):
|
||||
super().__init__()
|
||||
self.__default_value = value
|
||||
|
||||
@property
|
||||
def value(self) -> str:
|
||||
return self.__value or self.__default_value
|
||||
|
||||
@value.setter
|
||||
def value(self, value: str):
|
||||
if self.__value != value:
|
||||
self.__value = value
|
||||
self.changed.emit(self.__value)
|
||||
|
||||
|
||||
class Translator:
|
||||
_strings: dict[str, TranslatedString] = {}
|
||||
_lang: str = DEFAULT_TRANSLATION
|
||||
|
||||
def __init__(self):
|
||||
for k, v in self.__get_translation_dict(DEFAULT_TRANSLATION).items():
|
||||
self._strings[k] = TranslatedString(v)
|
||||
|
||||
def __get_translation_dict(self, lang: str) -> dict[str, str]:
|
||||
with open(
|
||||
Path(__file__).parents[2] / "resources" / "translations" / f"{lang}.json",
|
||||
encoding="utf-8",
|
||||
) as f:
|
||||
return ujson.loads(f.read())
|
||||
|
||||
def change_language(self, lang: str):
|
||||
self._lang = lang
|
||||
translated = self.__get_translation_dict(lang)
|
||||
for k in self._strings:
|
||||
self._strings[k].value = translated.get(k, None)
|
||||
|
||||
def translate_qobject(self, widget: QObject, key: str, **kwargs):
|
||||
"""Translates the text of the QObject using :func:`translate_with_setter`."""
|
||||
if isinstance(widget, (QLabel, QAction, QPushButton, QMessageBox, QPushButtonWrapper)):
|
||||
self.translate_with_setter(widget.setText, key, **kwargs)
|
||||
elif isinstance(widget, (QMenu)):
|
||||
self.translate_with_setter(widget.setTitle, key, **kwargs)
|
||||
else:
|
||||
raise RuntimeError
|
||||
|
||||
def translate_with_setter(self, setter: Callable[[str], None], key: str, **kwargs):
|
||||
"""Calls `setter` everytime the language changes and passes the translated string for `key`.
|
||||
|
||||
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)))
|
||||
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
|
||||
)
|
||||
return text
|
||||
|
||||
def translate_formatted(self, key: str, **kwargs) -> str:
|
||||
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"
|
||||
|
||||
|
||||
Translations = Translator()
|
||||
# Translations.change_language("de")
|
||||
@@ -84,6 +84,7 @@ from src.qt.modals.fix_unlinked import FixUnlinkedEntriesModal
|
||||
from src.qt.modals.folders_to_tags import FoldersToTagsModal
|
||||
from src.qt.modals.tag_database import TagDatabasePanel
|
||||
from src.qt.resource_manager import ResourceManager
|
||||
from src.qt.translations import Translations
|
||||
from src.qt.widgets.item_thumb import BadgeType, ItemThumb
|
||||
from src.qt.widgets.migration_modal import JsonMigrationModal
|
||||
from src.qt.widgets.panel import PanelModal
|
||||
@@ -186,10 +187,10 @@ class QtDriver(DriverMixin, QObject):
|
||||
|
||||
def open_library_from_dialog(self):
|
||||
dir = QFileDialog.getExistingDirectory(
|
||||
None,
|
||||
"Open/Create Library",
|
||||
"/",
|
||||
QFileDialog.Option.ShowDirsOnly,
|
||||
parent=None,
|
||||
caption=Translations["window.title.open_create_library"],
|
||||
dir="/",
|
||||
options=QFileDialog.Option.ShowDirsOnly,
|
||||
)
|
||||
if dir not in (None, ""):
|
||||
self.open_library(Path(dir))
|
||||
@@ -254,15 +255,22 @@ class QtDriver(DriverMixin, QObject):
|
||||
self.main_window.setMenuBar(menu_bar)
|
||||
menu_bar.setNativeMenuBar(True)
|
||||
|
||||
file_menu = QMenu("&File", menu_bar)
|
||||
edit_menu = QMenu("&Edit", menu_bar)
|
||||
view_menu = QMenu("&View", menu_bar)
|
||||
tools_menu = QMenu("&Tools", menu_bar)
|
||||
macros_menu = QMenu("&Macros", menu_bar)
|
||||
help_menu = QMenu("&Help", menu_bar)
|
||||
file_menu = QMenu(menu_bar)
|
||||
Translations.translate_qobject(file_menu, "menu.file")
|
||||
edit_menu = QMenu(menu_bar)
|
||||
Translations.translate_qobject(edit_menu, "generic.edit_alt")
|
||||
view_menu = QMenu(menu_bar)
|
||||
Translations.translate_qobject(view_menu, "menu.view")
|
||||
tools_menu = QMenu(menu_bar)
|
||||
Translations.translate_qobject(tools_menu, "menu.tools")
|
||||
macros_menu = QMenu(menu_bar)
|
||||
Translations.translate_qobject(macros_menu, "menu.macros")
|
||||
help_menu = QMenu(menu_bar)
|
||||
Translations.translate_qobject(help_menu, "menu.help")
|
||||
|
||||
# File Menu ============================================================
|
||||
open_library_action = QAction("&Open/Create Library", menu_bar)
|
||||
open_library_action = QAction(menu_bar)
|
||||
Translations.translate_qobject(open_library_action, "menu.file.open_create_library")
|
||||
open_library_action.triggered.connect(lambda: self.open_library_from_dialog())
|
||||
open_library_action.setShortcut(
|
||||
QtCore.QKeyCombination(
|
||||
@@ -273,7 +281,8 @@ class QtDriver(DriverMixin, QObject):
|
||||
open_library_action.setToolTip("Ctrl+O")
|
||||
file_menu.addAction(open_library_action)
|
||||
|
||||
save_library_backup_action = QAction("&Save Library Backup", menu_bar)
|
||||
save_library_backup_action = QAction(menu_bar)
|
||||
Translations.translate_qobject(save_library_backup_action, "menu.file.save_backup")
|
||||
save_library_backup_action.triggered.connect(
|
||||
lambda: self.callback_library_needed_check(self.backup_library)
|
||||
)
|
||||
@@ -291,7 +300,8 @@ class QtDriver(DriverMixin, QObject):
|
||||
|
||||
file_menu.addSeparator()
|
||||
|
||||
add_new_files_action = QAction("&Refresh Directories", menu_bar)
|
||||
add_new_files_action = QAction(menu_bar)
|
||||
Translations.translate_qobject(add_new_files_action, "menu.file.refresh_directories")
|
||||
add_new_files_action.triggered.connect(
|
||||
lambda: self.callback_library_needed_check(self.add_new_files_callback)
|
||||
)
|
||||
@@ -305,12 +315,14 @@ class QtDriver(DriverMixin, QObject):
|
||||
file_menu.addAction(add_new_files_action)
|
||||
file_menu.addSeparator()
|
||||
|
||||
close_library_action = QAction("&Close Library", menu_bar)
|
||||
close_library_action = QAction(menu_bar)
|
||||
Translations.translate_qobject(close_library_action, "menu.file.close_library")
|
||||
close_library_action.triggered.connect(self.close_library)
|
||||
file_menu.addAction(close_library_action)
|
||||
file_menu.addSeparator()
|
||||
|
||||
open_on_start_action = QAction("Open Library on Start", self)
|
||||
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))
|
||||
@@ -321,7 +333,8 @@ class QtDriver(DriverMixin, QObject):
|
||||
file_menu.addAction(open_on_start_action)
|
||||
|
||||
# Edit Menu ============================================================
|
||||
new_tag_action = QAction("New &Tag", menu_bar)
|
||||
new_tag_action = QAction(menu_bar)
|
||||
Translations.translate_qobject(new_tag_action, "menu.edit.new_tag")
|
||||
new_tag_action.triggered.connect(lambda: self.add_tag_action_callback())
|
||||
new_tag_action.setShortcut(
|
||||
QtCore.QKeyCombination(
|
||||
@@ -334,7 +347,8 @@ class QtDriver(DriverMixin, QObject):
|
||||
|
||||
edit_menu.addSeparator()
|
||||
|
||||
select_all_action = QAction("Select All", menu_bar)
|
||||
select_all_action = QAction(menu_bar)
|
||||
Translations.translate_qobject(select_all_action, "select.all")
|
||||
select_all_action.triggered.connect(self.select_all_action_callback)
|
||||
select_all_action.setShortcut(
|
||||
QtCore.QKeyCombination(
|
||||
@@ -345,7 +359,8 @@ class QtDriver(DriverMixin, QObject):
|
||||
select_all_action.setToolTip("Ctrl+A")
|
||||
edit_menu.addAction(select_all_action)
|
||||
|
||||
clear_select_action = QAction("Clear Selection", menu_bar)
|
||||
clear_select_action = QAction(menu_bar)
|
||||
Translations.translate_qobject(clear_select_action, "select.clear")
|
||||
clear_select_action.triggered.connect(self.clear_select_action_callback)
|
||||
clear_select_action.setShortcut(QtCore.Qt.Key.Key_Escape)
|
||||
clear_select_action.setToolTip("Esc")
|
||||
@@ -353,16 +368,21 @@ class QtDriver(DriverMixin, QObject):
|
||||
|
||||
edit_menu.addSeparator()
|
||||
|
||||
manage_file_extensions_action = QAction("Manage File Extensions", menu_bar)
|
||||
manage_file_extensions_action = QAction(menu_bar)
|
||||
Translations.translate_qobject(
|
||||
manage_file_extensions_action, "menu.edit.manage_file_extensions"
|
||||
)
|
||||
manage_file_extensions_action.triggered.connect(self.show_file_extension_modal)
|
||||
edit_menu.addAction(manage_file_extensions_action)
|
||||
|
||||
tag_database_action = QAction("Manage Tags", menu_bar)
|
||||
tag_database_action = QAction(menu_bar)
|
||||
Translations.translate_qobject(tag_database_action, "menu.edit.manage_tags")
|
||||
tag_database_action.triggered.connect(lambda: self.show_tag_database())
|
||||
edit_menu.addAction(tag_database_action)
|
||||
|
||||
# View Menu ============================================================
|
||||
show_libs_list_action = QAction("Show Recent Libraries", menu_bar)
|
||||
show_libs_list_action = QAction(menu_bar)
|
||||
Translations.translate_qobject(show_libs_list_action, "settings.show_recent_libraries")
|
||||
show_libs_list_action.setCheckable(True)
|
||||
show_libs_list_action.setChecked(
|
||||
bool(self.settings.value(SettingItems.WINDOW_SHOW_LIBS, defaultValue=True, type=bool))
|
||||
@@ -375,7 +395,8 @@ class QtDriver(DriverMixin, QObject):
|
||||
)
|
||||
view_menu.addAction(show_libs_list_action)
|
||||
|
||||
show_filenames_action = QAction("Show Filenames in Grid", menu_bar)
|
||||
show_filenames_action = QAction(menu_bar)
|
||||
Translations.translate_qobject(show_filenames_action, "settings.show_filenames_in_grid")
|
||||
show_filenames_action.setCheckable(True)
|
||||
show_filenames_action.setChecked(
|
||||
bool(self.settings.value(SettingItems.SHOW_FILENAMES, defaultValue=True, type=bool))
|
||||
@@ -394,7 +415,10 @@ class QtDriver(DriverMixin, QObject):
|
||||
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(menu_bar)
|
||||
Translations.translate_qobject(
|
||||
fix_unlinked_entries_action, "menu.tools.fix_unlinked_entries"
|
||||
)
|
||||
fix_unlinked_entries_action.triggered.connect(create_fix_unlinked_entries_modal)
|
||||
tools_menu.addAction(fix_unlinked_entries_action)
|
||||
|
||||
@@ -403,7 +427,8 @@ class QtDriver(DriverMixin, QObject):
|
||||
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(menu_bar)
|
||||
Translations.translate_qobject(fix_dupe_files_action, "menu.tools.fix_duplicate_files")
|
||||
fix_dupe_files_action.triggered.connect(create_dupe_files_modal)
|
||||
tools_menu.addAction(fix_dupe_files_action)
|
||||
|
||||
@@ -426,12 +451,14 @@ class QtDriver(DriverMixin, QObject):
|
||||
self.folders_modal = FoldersToTagsModal(self.lib, self)
|
||||
self.folders_modal.show()
|
||||
|
||||
folders_to_tags_action = QAction("Folders to Tags", menu_bar)
|
||||
folders_to_tags_action = QAction(menu_bar)
|
||||
Translations.translate_qobject(folders_to_tags_action, "menu.macros.folders_to_tags")
|
||||
folders_to_tags_action.triggered.connect(create_folders_tags_modal)
|
||||
macros_menu.addAction(folders_to_tags_action)
|
||||
|
||||
# Help Menu ============================================================
|
||||
self.repo_action = QAction("Visit GitHub Repository", menu_bar)
|
||||
self.repo_action = QAction(menu_bar)
|
||||
Translations.translate_qobject(self.repo_action, "help.visit_github")
|
||||
self.repo_action.triggered.connect(
|
||||
lambda: webbrowser.open("https://github.com/TagStudioDev/TagStudio")
|
||||
)
|
||||
@@ -455,12 +482,13 @@ class QtDriver(DriverMixin, QObject):
|
||||
str(Path(__file__).parents[2] / "resources/qt/fonts/Oxanium-Bold.ttf")
|
||||
)
|
||||
|
||||
# TODO this doesn't update when the language is changed
|
||||
self.thumb_sizes: list[tuple[str, int]] = [
|
||||
("Extra Large Thumbnails", 256),
|
||||
("Large Thumbnails", 192),
|
||||
("Medium Thumbnails", 128),
|
||||
("Small Thumbnails", 96),
|
||||
("Mini Thumbnails", 76),
|
||||
(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),
|
||||
]
|
||||
self.item_thumbs: list[ItemThumb] = []
|
||||
self.thumb_renderers: list[ThumbRenderer] = []
|
||||
@@ -468,11 +496,13 @@ class QtDriver(DriverMixin, QObject):
|
||||
self.init_library_window()
|
||||
self.migration_modal: JsonMigrationModal = None
|
||||
|
||||
path_result = self.evaluate_path(self.args.open)
|
||||
path_result = self.evaluate_path(str(self.args.open).lstrip().rstrip())
|
||||
# check status of library path evaluating
|
||||
if path_result.success and path_result.library_path:
|
||||
self.splash.showMessage(
|
||||
f'Opening Library "{path_result.library_path}"...',
|
||||
Translations.translate_formatted(
|
||||
"splash.opening_library", library_path=path_result.library_path
|
||||
),
|
||||
int(Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignHCenter),
|
||||
QColor("#9782ff"),
|
||||
)
|
||||
@@ -489,8 +519,8 @@ class QtDriver(DriverMixin, QObject):
|
||||
msg_box = QMessageBox()
|
||||
msg_box.setIcon(QMessageBox.Icon.Critical)
|
||||
msg_box.setText(message)
|
||||
msg_box.setWindowTitle("Error")
|
||||
msg_box.addButton("Close", QMessageBox.ButtonRole.AcceptRole)
|
||||
msg_box.setWindowTitle(Translations["window.title.error"])
|
||||
msg_box.addButton(Translations["generic.close"], QMessageBox.ButtonRole.AcceptRole)
|
||||
|
||||
# Show the message box
|
||||
msg_box.exec()
|
||||
@@ -584,7 +614,7 @@ class QtDriver(DriverMixin, QObject):
|
||||
return
|
||||
|
||||
logger.info("Closing Library...")
|
||||
self.main_window.statusbar.showMessage("Closing Library...")
|
||||
self.main_window.statusbar.showMessage(Translations["status.library_closing"])
|
||||
start_time = time.time()
|
||||
|
||||
self.settings.setValue(SettingItems.LAST_LIBRARY, str(self.lib.library_dir))
|
||||
@@ -610,27 +640,33 @@ class QtDriver(DriverMixin, QObject):
|
||||
|
||||
end_time = time.time()
|
||||
self.main_window.statusbar.showMessage(
|
||||
f"Library Closed ({format_timespan(end_time - start_time)})"
|
||||
Translations.translate_formatted(
|
||||
"status.library_closed", time_span=format_timespan(end_time - start_time)
|
||||
)
|
||||
)
|
||||
|
||||
def backup_library(self):
|
||||
logger.info("Backing Up Library...")
|
||||
self.main_window.statusbar.showMessage("Saving Library...")
|
||||
self.main_window.statusbar.showMessage(Translations["status.library_backup_in_progress"])
|
||||
start_time = time.time()
|
||||
target_path = self.lib.save_library_backup_to_disk()
|
||||
end_time = time.time()
|
||||
self.main_window.statusbar.showMessage(
|
||||
f'Library Backup Saved at: "{target_path}" ({format_timespan(end_time - start_time)})'
|
||||
Translations.translate_formatted(
|
||||
"status.library_backup_success",
|
||||
path=target_path,
|
||||
time_span=format_timespan(end_time - start_time),
|
||||
)
|
||||
)
|
||||
|
||||
def add_tag_action_callback(self):
|
||||
panel = BuildTagPanel(self.lib)
|
||||
self.modal = PanelModal(
|
||||
panel,
|
||||
"New Tag",
|
||||
"Add Tag",
|
||||
has_save=True,
|
||||
)
|
||||
Translations.translate_with_setter(self.modal.setTitle, "tag.new")
|
||||
Translations.translate_with_setter(self.modal.setWindowTitle, "tag.add")
|
||||
|
||||
self.modal.saved.connect(
|
||||
lambda: (
|
||||
@@ -665,21 +701,21 @@ class QtDriver(DriverMixin, QObject):
|
||||
def show_tag_database(self):
|
||||
self.modal = PanelModal(
|
||||
widget=TagDatabasePanel(self.lib),
|
||||
title="Library Tags",
|
||||
window_title="Library Tags",
|
||||
done_callback=self.preview_panel.update_widgets,
|
||||
has_save=False,
|
||||
)
|
||||
Translations.translate_with_setter(self.modal.setTitle, "tag_manager.title")
|
||||
Translations.translate_with_setter(self.modal.setWindowTitle, "tag_manager.title")
|
||||
self.modal.show()
|
||||
|
||||
def show_file_extension_modal(self):
|
||||
panel = FileExtensionModal(self.lib)
|
||||
self.modal = PanelModal(
|
||||
panel,
|
||||
"File Extensions",
|
||||
"File Extensions",
|
||||
has_save=True,
|
||||
)
|
||||
Translations.translate_with_setter(self.modal.setTitle, "ignore_list.title")
|
||||
Translations.translate_with_setter(self.modal.setWindowTitle, "ignore_list.title")
|
||||
|
||||
self.modal.saved.connect(lambda: (panel.save(), self.filter_items()))
|
||||
self.modal.show()
|
||||
@@ -689,12 +725,13 @@ class QtDriver(DriverMixin, QObject):
|
||||
tracker = RefreshDirTracker(self.lib)
|
||||
|
||||
pw = ProgressWidget(
|
||||
window_title="Refreshing Directories",
|
||||
label_text="Scanning Directories for New Files...\nPreparing...",
|
||||
cancel_button_text=None,
|
||||
minimum=0,
|
||||
maximum=0,
|
||||
)
|
||||
Translations.translate_with_setter(pw.setWindowTitle, "library.refresh.title")
|
||||
Translations.translate_with_setter(pw.update_label, "library.refresh.scanning_preparing")
|
||||
|
||||
pw.show()
|
||||
|
||||
iterator = FunctionIterator(lambda: tracker.refresh_dir(self.lib.library_dir))
|
||||
@@ -702,9 +739,13 @@ class QtDriver(DriverMixin, QObject):
|
||||
lambda x: (
|
||||
pw.update_progress(x + 1),
|
||||
pw.update_label(
|
||||
f"Scanning Directories for New Files...\n{x + 1}"
|
||||
f' File{"s" if x + 1 != 1 else ""} Searched,'
|
||||
f" {tracker.files_count} New Files Found"
|
||||
Translations.translate_formatted(
|
||||
"library.refresh.scanning.plural"
|
||||
if x + 1 != 1
|
||||
else "library.refresh.scanning.singular",
|
||||
searched_count=x + 1,
|
||||
found_count=tracker.files_count,
|
||||
)
|
||||
),
|
||||
)
|
||||
)
|
||||
@@ -727,18 +768,24 @@ class QtDriver(DriverMixin, QObject):
|
||||
|
||||
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",
|
||||
cancel_button_text=None,
|
||||
minimum=0,
|
||||
maximum=files_count,
|
||||
)
|
||||
Translations.translate_with_setter(pw.setWindowTitle, "macros.running.dialog.title")
|
||||
Translations.translate_with_setter(
|
||||
pw.update_label, "macros.running.dialog.new_entries", count=1, total=files_count
|
||||
)
|
||||
pw.show()
|
||||
|
||||
iterator.value.connect(
|
||||
lambda x: (
|
||||
pw.update_progress(x + 1),
|
||||
pw.update_label(f"Running Configured Macros on {x + 1}/{files_count} New Entries"),
|
||||
pw.update_label(
|
||||
Translations.translate_formatted(
|
||||
"macros.running.dialog.new_entries", count=x + 1, total=files_count
|
||||
)
|
||||
),
|
||||
)
|
||||
)
|
||||
r = CustomRunnable(iterator.run)
|
||||
@@ -1134,7 +1181,7 @@ class QtDriver(DriverMixin, QObject):
|
||||
self.filter = dataclasses.replace(self.filter, **dataclasses.asdict(filter))
|
||||
|
||||
# inform user about running search
|
||||
self.main_window.statusbar.showMessage("Searching Library...")
|
||||
self.main_window.statusbar.showMessage(Translations["status.library_search_query"])
|
||||
self.main_window.statusbar.repaint()
|
||||
|
||||
# search the library
|
||||
@@ -1149,7 +1196,11 @@ class QtDriver(DriverMixin, QObject):
|
||||
|
||||
# inform user about completed search
|
||||
self.main_window.statusbar.showMessage(
|
||||
f"{results.total_count} Results Found ({format_timespan(end_time - start_time)})"
|
||||
Translations.translate_formatted(
|
||||
"status.results_found",
|
||||
count=results.total_count,
|
||||
time_span=format_timespan(end_time - start_time),
|
||||
)
|
||||
)
|
||||
|
||||
# update page content
|
||||
@@ -1196,9 +1247,13 @@ class QtDriver(DriverMixin, QObject):
|
||||
|
||||
def open_library(self, path: Path) -> None:
|
||||
"""Open a TagStudio library."""
|
||||
open_message: str = f'Opening Library "{str(path)}"...'
|
||||
self.main_window.landing_widget.set_status_label(open_message)
|
||||
self.main_window.statusbar.showMessage(open_message, 3)
|
||||
translation_params = {"key": "splash.opening_library", "library_path": str(path)}
|
||||
Translations.translate_with_setter(
|
||||
self.main_window.landing_widget.set_status_label, **translation_params
|
||||
)
|
||||
self.main_window.statusbar.showMessage(
|
||||
Translations.translate_formatted(**translation_params), 3
|
||||
)
|
||||
self.main_window.repaint()
|
||||
|
||||
open_status: LibraryStatus = self.lib.open_library(path)
|
||||
@@ -1216,7 +1271,9 @@ class QtDriver(DriverMixin, QObject):
|
||||
|
||||
def init_library(self, path: Path, open_status: LibraryStatus):
|
||||
if not open_status.success:
|
||||
self.show_error_message(open_status.message or "Error opening library.")
|
||||
self.show_error_message(
|
||||
open_status.message or Translations["window.message.error_opening_library"]
|
||||
)
|
||||
return open_status
|
||||
|
||||
self.init_workers()
|
||||
@@ -1228,8 +1285,12 @@ class QtDriver(DriverMixin, QObject):
|
||||
self.add_new_files_callback()
|
||||
|
||||
self.update_libs_list(path)
|
||||
title_text = f"{self.base_title} - Library '{self.lib.library_dir}'"
|
||||
self.main_window.setWindowTitle(title_text)
|
||||
Translations.translate_with_setter(
|
||||
self.main_window.setWindowTitle,
|
||||
"app.title",
|
||||
base_title=self.base_title,
|
||||
library_dir=self.lib.library_dir,
|
||||
)
|
||||
self.main_window.setAcceptDrops(True)
|
||||
|
||||
self.selected.clear()
|
||||
|
||||
@@ -30,6 +30,7 @@ from src.core.media_types import MediaCategories, MediaType
|
||||
from src.qt.flowlayout import FlowWidget
|
||||
from src.qt.helpers.file_opener import FileOpenerHelper
|
||||
from src.qt.platform_strings import PlatformStrings
|
||||
from src.qt.translations import Translations
|
||||
from src.qt.widgets.thumb_button import ThumbButton
|
||||
from src.qt.widgets.thumb_renderer import ThumbRenderer
|
||||
|
||||
@@ -217,7 +218,8 @@ class ItemThumb(FlowWidget):
|
||||
self.thumb_button.setContextMenuPolicy(Qt.ContextMenuPolicy.ActionsContextMenu)
|
||||
|
||||
self.opener = FileOpenerHelper("")
|
||||
open_file_action = QAction("Open file", self)
|
||||
open_file_action = QAction(self)
|
||||
Translations.translate_qobject(open_file_action, "file.open_file")
|
||||
open_file_action.triggered.connect(self.opener.open_file)
|
||||
open_explorer_action = QAction(PlatformStrings.open_file_str, self)
|
||||
open_explorer_action.triggered.connect(self.opener.open_explorer)
|
||||
@@ -306,7 +308,8 @@ class ItemThumb(FlowWidget):
|
||||
self.cb_layout.addWidget(badge)
|
||||
|
||||
# Filename Label =======================================================
|
||||
self.file_label = QLabel(text="Filename")
|
||||
self.file_label = QLabel()
|
||||
Translations.translate_qobject(self.file_label, "generic.filename")
|
||||
self.file_label.setStyleSheet(ItemThumb.filename_style)
|
||||
self.file_label.setMaximumHeight(self.label_height)
|
||||
if not show_filename_label:
|
||||
|
||||
@@ -13,6 +13,7 @@ from PySide6.QtCore import QEasingCurve, QPoint, QPropertyAnimation, Qt
|
||||
from PySide6.QtGui import QPixmap
|
||||
from PySide6.QtWidgets import QLabel, QPushButton, QVBoxLayout, QWidget
|
||||
from src.qt.helpers.color_overlay import gradient_overlay, theme_fg_overlay
|
||||
from src.qt.translations import Translations
|
||||
from src.qt.widgets.clickable_label import ClickableLabel
|
||||
|
||||
# Only import for type checking/autocompletion, will not be imported at runtime.
|
||||
@@ -62,7 +63,9 @@ class LandingWidget(QWidget):
|
||||
open_shortcut_text = "(Ctrl+O)"
|
||||
self.open_button: QPushButton = QPushButton()
|
||||
self.open_button.setMinimumWidth(200)
|
||||
self.open_button.setText(f"Open/Create Library {open_shortcut_text}")
|
||||
Translations.translate_qobject(
|
||||
self.open_button, "landing.open_create_library", shortcut=open_shortcut_text
|
||||
)
|
||||
self.open_button.clicked.connect(self.driver.open_library_from_dialog)
|
||||
|
||||
# Create status label --------------------------------------------------
|
||||
|
||||
@@ -30,6 +30,7 @@ from src.core.library.json.library import Library as JsonLibrary # type: ignore
|
||||
from src.qt.helpers.custom_runnable import CustomRunnable
|
||||
from src.qt.helpers.function_iterator import FunctionIterator
|
||||
from src.qt.helpers.qbutton_wrapper import QPushButtonWrapper
|
||||
from src.qt.translations import Translations
|
||||
from src.qt.widgets.paged_panel.paged_body_wrapper import PagedBodyWrapper
|
||||
from src.qt.widgets.paged_panel.paged_panel import PagedPanel
|
||||
from src.qt.widgets.paged_panel.paged_panel_state import PagedPanelState
|
||||
@@ -54,7 +55,7 @@ class JsonMigrationModal(QObject):
|
||||
self.is_migration_initialized: bool = False
|
||||
self.discrepancies: list[str] = []
|
||||
|
||||
self.title: str = f'Save Format Migration: "{self.path}"'
|
||||
self.title: str = Translations.translate_formatted("json_migration.title", path=self.path)
|
||||
self.warning: str = "<b><a style='color: #e22c3c'>(!)</a></b>"
|
||||
|
||||
self.old_entry_count: int = 0
|
||||
@@ -77,24 +78,17 @@ class JsonMigrationModal(QObject):
|
||||
def init_page_info(self) -> None:
|
||||
"""Initialize the migration info page."""
|
||||
body_wrapper: PagedBodyWrapper = PagedBodyWrapper()
|
||||
body_label: QLabel = QLabel(
|
||||
"Library save files created with TagStudio versions <b>9.4 and below</b> will "
|
||||
"need to be migrated to the new <b>v9.5+</b> format."
|
||||
"<br>"
|
||||
"<h2>What you need to know:</h2>"
|
||||
"<ul>"
|
||||
"<li>Your existing library save file will <b><i>NOT</i></b> be deleted</li>"
|
||||
"<li>Your personal files will <b><i>NOT</i></b> be deleted, moved, or modified</li>"
|
||||
"<li>The new v9.5+ save format can not be opened in earlier versions of TagStudio</li>"
|
||||
"</ul>"
|
||||
)
|
||||
body_label = QLabel()
|
||||
Translations.translate_qobject(body_label, "json_migration.info.description")
|
||||
body_label.setWordWrap(True)
|
||||
body_label.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
||||
body_wrapper.layout().addWidget(body_label)
|
||||
body_wrapper.layout().setContentsMargins(0, 36, 0, 0)
|
||||
|
||||
cancel_button: QPushButtonWrapper = QPushButtonWrapper("Cancel")
|
||||
next_button: QPushButtonWrapper = QPushButtonWrapper("Continue")
|
||||
cancel_button = QPushButtonWrapper()
|
||||
Translations.translate_qobject(cancel_button, "generic.cancel")
|
||||
next_button = QPushButtonWrapper()
|
||||
Translations.translate_qobject(next_button, "generic.continue")
|
||||
cancel_button.clicked.connect(self.migration_cancelled.emit)
|
||||
|
||||
self.stack.append(
|
||||
@@ -115,30 +109,20 @@ class JsonMigrationModal(QObject):
|
||||
body_container_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
tab: str = " "
|
||||
self.match_text: str = "Matched"
|
||||
self.differ_text: str = "Discrepancy"
|
||||
self.match_text: str = Translations["json_migration.heading.match"]
|
||||
self.differ_text: str = Translations["json_migration.heading.differ"]
|
||||
|
||||
entries_text: str = "Entries:"
|
||||
tags_text: str = "Tags:"
|
||||
shorthand_text: str = tab + "Shorthands:"
|
||||
subtags_text: str = tab + "Parent Tags:"
|
||||
aliases_text: str = tab + "Aliases:"
|
||||
colors_text: str = tab + "Colors:"
|
||||
ext_text: str = "File Extension List:"
|
||||
ext_type_text: str = "Extension List Type:"
|
||||
desc_text: str = (
|
||||
"<br>Start and preview the results of the library migration process. "
|
||||
'The converted library will <i>not</i> be used unless you click "Finish Migration". '
|
||||
"<br><br>"
|
||||
'Library data should either have matching values or a feature a "Matched" label. '
|
||||
'Values that do not match will be displayed in red and feature a "<b>(!)</b>" '
|
||||
"symbol next to them."
|
||||
"<br><center><i>"
|
||||
"This process may take up to several minutes for larger libraries."
|
||||
"</i></center>"
|
||||
)
|
||||
path_parity_text: str = tab + "Paths:"
|
||||
field_parity_text: str = tab + "Fields:"
|
||||
entries_text: str = Translations["json_migration.heading.entires"]
|
||||
tags_text: str = Translations["json_migration.heading.tags"]
|
||||
shorthand_text: str = tab + Translations["json_migration.heading.shorthands"]
|
||||
subtags_text: str = tab + Translations["json_migration.heading.parent_tags"]
|
||||
aliases_text: str = tab + Translations["json_migration.heading.aliases"]
|
||||
colors_text: str = tab + Translations["json_migration.heading.colors"]
|
||||
ext_text: str = Translations["json_migration.heading.file_extension_list"]
|
||||
ext_type_text: str = Translations["json_migration.heading.extension_list_type"]
|
||||
desc_text: str = Translations["json_migration.description"]
|
||||
path_parity_text: str = tab + Translations["json_migration.heading.paths"]
|
||||
field_parity_text: str = tab + Translations["json_migration.heading.fields"]
|
||||
|
||||
self.entries_row: int = 0
|
||||
self.path_row: int = 1
|
||||
@@ -153,7 +137,8 @@ class JsonMigrationModal(QObject):
|
||||
|
||||
old_lib_container: QWidget = QWidget()
|
||||
old_lib_layout: QVBoxLayout = QVBoxLayout(old_lib_container)
|
||||
old_lib_title: QLabel = QLabel("<h2>v9.4 Library</h2>")
|
||||
old_lib_title = QLabel()
|
||||
Translations.translate_qobject(old_lib_title, "json_migration.title.old_lib")
|
||||
old_lib_title.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
old_lib_layout.addWidget(old_lib_title)
|
||||
|
||||
@@ -215,7 +200,8 @@ class JsonMigrationModal(QObject):
|
||||
|
||||
new_lib_container: QWidget = QWidget()
|
||||
new_lib_layout: QVBoxLayout = QVBoxLayout(new_lib_container)
|
||||
new_lib_title: QLabel = QLabel("<h2>v9.5+ Library</h2>")
|
||||
new_lib_title = QLabel()
|
||||
Translations.translate_qobject(new_lib_title, "json_migration.title.new_lib")
|
||||
new_lib_title.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
new_lib_layout.addWidget(new_lib_title)
|
||||
|
||||
@@ -291,13 +277,16 @@ class JsonMigrationModal(QObject):
|
||||
self.body_wrapper_01.layout().addWidget(desc_label)
|
||||
self.body_wrapper_01.layout().setSpacing(12)
|
||||
|
||||
back_button: QPushButtonWrapper = QPushButtonWrapper("Back")
|
||||
start_button: QPushButtonWrapper = QPushButtonWrapper("Start and Preview")
|
||||
back_button = QPushButtonWrapper()
|
||||
Translations.translate_qobject(back_button, "generic.navigation.back")
|
||||
start_button = QPushButtonWrapper()
|
||||
Translations.translate_qobject(start_button, "json_migration.start_and_preview")
|
||||
start_button.setMinimumWidth(120)
|
||||
start_button.clicked.connect(self.migrate)
|
||||
start_button.clicked.connect(lambda: finish_button.setDisabled(False))
|
||||
start_button.clicked.connect(lambda: start_button.setDisabled(True))
|
||||
finish_button: QPushButtonWrapper = QPushButtonWrapper("Finish Migration")
|
||||
finish_button: QPushButtonWrapper = QPushButtonWrapper()
|
||||
Translations.translate_qobject(finish_button, "json_migration.finish_migration")
|
||||
finish_button.setMinimumWidth(120)
|
||||
finish_button.setDisabled(True)
|
||||
finish_button.clicked.connect(self.finish_migration)
|
||||
@@ -348,9 +337,11 @@ class JsonMigrationModal(QObject):
|
||||
lambda x: (
|
||||
pb.setLabelText(f"<h4>{x}</h4>"),
|
||||
self.update_sql_value_ui(show_msg_box=False)
|
||||
if x == "Checking for Parity..."
|
||||
if x == Translations["json_migration.checking_for_parity"]
|
||||
else (),
|
||||
self.update_parity_ui()
|
||||
if x == Translations["json_migration.checking_for_parity"]
|
||||
else (),
|
||||
self.update_parity_ui() if x == "Checking for Parity..." else (),
|
||||
)
|
||||
)
|
||||
r = CustomRunnable(iterator.run)
|
||||
@@ -367,7 +358,7 @@ class JsonMigrationModal(QObject):
|
||||
"""Iterate over the library migration process."""
|
||||
try:
|
||||
# Convert JSON Library to SQLite
|
||||
yield "Creating SQL Database Tables..."
|
||||
yield Translations["json_migration.creating_database_tables"]
|
||||
self.sql_lib = SqliteLibrary()
|
||||
self.temp_path: Path = (
|
||||
self.json_lib.library_dir / TS_FOLDER_NAME / "migration_ts_library.sqlite"
|
||||
@@ -379,9 +370,11 @@ class JsonMigrationModal(QObject):
|
||||
self.sql_lib.open_sqlite_library(
|
||||
self.json_lib.library_dir, is_new=True, add_default_data=False
|
||||
)
|
||||
yield f"Migrating {len(self.json_lib.entries):,d} File Entries..."
|
||||
yield Translations.translate_formatted(
|
||||
"json_migration.migrating_files_entries", entries=len(self.json_lib.entries)
|
||||
)
|
||||
self.sql_lib.migrate_json_to_sqlite(self.json_lib)
|
||||
yield "Checking for Parity..."
|
||||
yield Translations["json_migration.checking_for_parity"]
|
||||
check_set = set()
|
||||
check_set.add(self.check_field_parity())
|
||||
check_set.add(self.check_path_parity())
|
||||
@@ -391,9 +384,9 @@ class JsonMigrationModal(QObject):
|
||||
check_set.add(self.check_color_parity())
|
||||
self.update_parity_ui()
|
||||
if False not in check_set:
|
||||
yield "Migration Complete!"
|
||||
yield Translations["json_migration.migration_complete"]
|
||||
else:
|
||||
yield "Migration Complete, Discrepancies Found"
|
||||
yield Translations["json_migration.migration_complete_with_discrepancies"]
|
||||
self.done = True
|
||||
|
||||
except Exception as e:
|
||||
@@ -440,10 +433,11 @@ class JsonMigrationModal(QObject):
|
||||
if not show_msg_box:
|
||||
return
|
||||
msg_box = QMessageBox()
|
||||
msg_box.setWindowTitle("Library Discrepancies Found")
|
||||
msg_box.setText(
|
||||
"Discrepancies were found between the original and converted library formats. "
|
||||
"Please review and choose to whether continue with the migration or to cancel."
|
||||
Translations.translate_with_setter(
|
||||
msg_box.setWindowTitle, "json_migration.discrepancies_found"
|
||||
)
|
||||
Translations.translate_qobject(
|
||||
msg_box, "json_migration.discrepancies_found.description"
|
||||
)
|
||||
msg_box.setDetailedText("\n".join(self.discrepancies))
|
||||
msg_box.setIcon(QMessageBox.Icon.Warning)
|
||||
|
||||
@@ -6,6 +6,7 @@ from typing import Callable
|
||||
|
||||
from PySide6.QtCore import Qt, Signal
|
||||
from PySide6.QtWidgets import QHBoxLayout, QLabel, QPushButton, QVBoxLayout, QWidget
|
||||
from src.qt.translations import Translations
|
||||
|
||||
|
||||
class PanelModal(QWidget):
|
||||
@@ -16,8 +17,8 @@ class PanelModal(QWidget):
|
||||
def __init__(
|
||||
self,
|
||||
widget: "PanelWidget",
|
||||
title: str,
|
||||
window_title: str,
|
||||
title: str = "",
|
||||
window_title: str = "",
|
||||
done_callback: Callable | None = None,
|
||||
save_callback: Callable | None = None,
|
||||
has_save: bool = False,
|
||||
@@ -36,7 +37,7 @@ class PanelModal(QWidget):
|
||||
self.title_widget.setObjectName("fieldTitle")
|
||||
self.title_widget.setWordWrap(True)
|
||||
self.title_widget.setStyleSheet("font-weight:bold;" "font-size:14px;" "padding-top: 6px")
|
||||
self.title_widget.setText(title)
|
||||
self.setTitle(title)
|
||||
self.title_widget.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
self.button_container = QWidget()
|
||||
@@ -49,7 +50,7 @@ class PanelModal(QWidget):
|
||||
|
||||
if not (save_callback or has_save):
|
||||
self.done_button = QPushButton()
|
||||
self.done_button.setText("Done")
|
||||
Translations.translate_qobject(self.done_button, "generic.done")
|
||||
self.done_button.setAutoDefault(True)
|
||||
self.done_button.clicked.connect(self.hide)
|
||||
if done_callback:
|
||||
@@ -59,7 +60,7 @@ class PanelModal(QWidget):
|
||||
|
||||
if save_callback or has_save:
|
||||
self.cancel_button = QPushButton()
|
||||
self.cancel_button.setText("Cancel")
|
||||
Translations.translate_qobject(self.cancel_button, "generic.cancel")
|
||||
self.cancel_button.clicked.connect(self.hide)
|
||||
self.cancel_button.clicked.connect(widget.reset)
|
||||
# self.cancel_button.clicked.connect(cancel_callback)
|
||||
@@ -67,7 +68,7 @@ class PanelModal(QWidget):
|
||||
self.button_layout.addWidget(self.cancel_button)
|
||||
|
||||
self.save_button = QPushButton()
|
||||
self.save_button.setText("Save")
|
||||
Translations.translate_qobject(self.save_button, "generic.save")
|
||||
self.save_button.setAutoDefault(True)
|
||||
self.save_button.clicked.connect(self.hide)
|
||||
self.save_button.clicked.connect(self.saved.emit)
|
||||
@@ -97,6 +98,9 @@ class PanelModal(QWidget):
|
||||
self.done_button.click()
|
||||
event.accept()
|
||||
|
||||
def setTitle(self, title: str): # noqa: N802
|
||||
self.title_widget.setText(title)
|
||||
|
||||
|
||||
class PanelWidget(QWidget):
|
||||
"""Used for widgets that go in a modal panel, ex. for editing or searching."""
|
||||
|
||||
@@ -52,6 +52,7 @@ from src.qt.helpers.qbutton_wrapper import QPushButtonWrapper
|
||||
from src.qt.helpers.rounded_pixmap_style import RoundedPixmapStyle
|
||||
from src.qt.modals.add_field import AddFieldModal
|
||||
from src.qt.platform_strings import PlatformStrings
|
||||
from src.qt.translations import Translations
|
||||
from src.qt.widgets.fields import FieldContainer
|
||||
from src.qt.widgets.media_player import MediaPlayer
|
||||
from src.qt.widgets.panel import PanelModal
|
||||
@@ -119,7 +120,8 @@ class PreviewPanel(QWidget):
|
||||
)
|
||||
date_style = "font-size:12px;"
|
||||
|
||||
self.open_file_action = QAction("Open file", self)
|
||||
self.open_file_action = QAction(self)
|
||||
Translations.translate_qobject(self.open_file_action, "file.open_file")
|
||||
self.open_explorer_action = QAction(PlatformStrings.open_file_str, self)
|
||||
|
||||
self.preview_img = QPushButtonWrapper()
|
||||
@@ -279,7 +281,7 @@ class PreviewPanel(QWidget):
|
||||
self.add_field_button.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
self.add_field_button.setMinimumSize(96, 28)
|
||||
self.add_field_button.setMaximumSize(96, 28)
|
||||
self.add_field_button.setText("Add Field")
|
||||
Translations.translate_qobject(self.add_field_button, "library.field.add")
|
||||
self.afb_layout.addWidget(self.add_field_button)
|
||||
self.add_field_modal = AddFieldModal(self.lib)
|
||||
self.place_add_field_button()
|
||||
@@ -303,7 +305,7 @@ class PreviewPanel(QWidget):
|
||||
self.driver.frame_content[grid_idx] = result
|
||||
|
||||
def remove_field_prompt(self, name: str) -> str:
|
||||
return f'Are you sure you want to remove field "{name}"?'
|
||||
return Translations.translate_formatted("library.field.confirm_remove", name=name)
|
||||
|
||||
def fill_libs_widget(self, layout: QVBoxLayout):
|
||||
settings = self.driver.settings
|
||||
@@ -341,7 +343,8 @@ class PreviewPanel(QWidget):
|
||||
# remove any potential previous items
|
||||
clear_layout(layout)
|
||||
|
||||
label = QLabel("Recent Libraries")
|
||||
label = QLabel()
|
||||
Translations.translate_qobject(label, "generic.recent_libraries")
|
||||
label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
row_layout = QHBoxLayout()
|
||||
@@ -379,7 +382,7 @@ class PreviewPanel(QWidget):
|
||||
lib = Path(full_val)
|
||||
if not lib.exists() or not (lib / TS_FOLDER_NAME).exists():
|
||||
button.setDisabled(True)
|
||||
button.setToolTip("Location is missing")
|
||||
Translations.translate_with_setter(button.setToolTip, "library.missing")
|
||||
|
||||
def open_library_button_clicked(path):
|
||||
return lambda: self.driver.open_library(Path(path))
|
||||
@@ -492,16 +495,16 @@ class PreviewPanel(QWidget):
|
||||
created = dt.fromtimestamp(filepath.stat().st_ctime)
|
||||
modified: dt = dt.fromtimestamp(filepath.stat().st_mtime)
|
||||
self.date_created_label.setText(
|
||||
f"<b>Date Created:</b> {dt.strftime(created, "%a, %x, %X")}"
|
||||
f"<b>Date Created:</b> {dt.strftime(created, "%a, %x, %X")}" # TODO translate
|
||||
)
|
||||
self.date_modified_label.setText(
|
||||
f"<b>Date Modified:</b> {dt.strftime(modified, "%a, %x, %X")}"
|
||||
f"<b>Date Modified:</b> {dt.strftime(modified, "%a, %x, %X")}" # TODO translate
|
||||
)
|
||||
self.date_created_label.setHidden(False)
|
||||
self.date_modified_label.setHidden(False)
|
||||
elif filepath:
|
||||
self.date_created_label.setText("<b>Date Created:</b> <i>N/A</i>")
|
||||
self.date_modified_label.setText("<b>Date Modified:</b> <i>N/A</i>")
|
||||
self.date_created_label.setText("<b>Date Created:</b> <i>N/A</i>") # TODO translate
|
||||
self.date_modified_label.setText("<b>Date Modified:</b> <i>N/A</i>") # TODO translate
|
||||
self.date_created_label.setHidden(False)
|
||||
self.date_modified_label.setHidden(False)
|
||||
else:
|
||||
@@ -520,7 +523,7 @@ class PreviewPanel(QWidget):
|
||||
|
||||
if not self.driver.selected:
|
||||
if self.selected or not self.initialized:
|
||||
self.file_label.setText("<i>No Items Selected</i>")
|
||||
self.file_label.setText("<i>No Items Selected</i>") # TODO translate
|
||||
self.file_label.set_file_path("")
|
||||
self.file_label.setCursor(Qt.CursorShape.ArrowCursor)
|
||||
|
||||
@@ -777,7 +780,9 @@ class PreviewPanel(QWidget):
|
||||
self.media_player.hide()
|
||||
self.update_date_label()
|
||||
if self.selected != self.driver.selected:
|
||||
self.file_label.setText(f"<b>{len(self.driver.selected)}</b> Items Selected")
|
||||
self.file_label.setText(
|
||||
f"<b>{len(self.driver.selected)}</b> Items Selected"
|
||||
) # TODO translate
|
||||
self.file_label.setCursor(Qt.CursorShape.ArrowCursor)
|
||||
self.file_label.set_file_path("")
|
||||
self.dimensions_label.setText("")
|
||||
@@ -872,10 +877,11 @@ class PreviewPanel(QWidget):
|
||||
else:
|
||||
container = self.containers[index]
|
||||
|
||||
# TODO this is in severe need of refactoring due to exessive code duplication
|
||||
if isinstance(field, TagBoxField):
|
||||
container.set_title(field.type.name)
|
||||
container.set_inline(False)
|
||||
title = f"{field.type.name} (Tag Box)"
|
||||
title = f"{field.type.name} (Tag Box)" # TODO translate
|
||||
|
||||
if not is_mixed:
|
||||
inner_container = container.get_inner_widget()
|
||||
@@ -916,8 +922,8 @@ class PreviewPanel(QWidget):
|
||||
)
|
||||
)
|
||||
else:
|
||||
text = "<i>Mixed Data</i>"
|
||||
title = f"{field.type.name} (Wacky Tag Box)"
|
||||
text = "<i>Mixed Data</i>" # TODO translate
|
||||
title = f"{field.type.name} (Wacky Tag Box)" # TODO translate
|
||||
inner_container = TextWidget(title, text)
|
||||
container.set_inner_widget(inner_container)
|
||||
|
||||
@@ -932,7 +938,7 @@ class PreviewPanel(QWidget):
|
||||
assert isinstance(field.value, (str, type(None)))
|
||||
text = field.value or ""
|
||||
else:
|
||||
text = "<i>Mixed Data</i>"
|
||||
text = "<i>Mixed Data</i>" # TODO translate
|
||||
|
||||
title = f"{field.type.name} ({field.type.type.value})"
|
||||
inner_container = TextWidget(title, text)
|
||||
@@ -941,7 +947,7 @@ class PreviewPanel(QWidget):
|
||||
modal = PanelModal(
|
||||
EditTextLine(field.value),
|
||||
title=title,
|
||||
window_title=f"Edit {field.type.type.value}",
|
||||
window_title=f"Edit {field.type.type.value}", # TODO translate
|
||||
save_callback=(
|
||||
lambda content: (
|
||||
self.update_field(field, content),
|
||||
@@ -973,15 +979,15 @@ class PreviewPanel(QWidget):
|
||||
assert isinstance(field.value, (str, type(None)))
|
||||
text = (field.value or "").replace("\r", "\n")
|
||||
else:
|
||||
text = "<i>Mixed Data</i>"
|
||||
title = f"{field.type.name} (Text Box)"
|
||||
text = "<i>Mixed Data</i>" # TODO translate
|
||||
title = f"{field.type.name} (Text Box)" # TODO translate
|
||||
inner_container = TextWidget(title, text)
|
||||
container.set_inner_widget(inner_container)
|
||||
if not is_mixed:
|
||||
modal = PanelModal(
|
||||
EditTextBox(field.value),
|
||||
title=title,
|
||||
window_title=f"Edit {field.type.name}",
|
||||
window_title=f"Edit {field.type.name}", # TODO translate
|
||||
save_callback=(
|
||||
lambda content: (
|
||||
self.update_field(field, content),
|
||||
@@ -1008,14 +1014,14 @@ class PreviewPanel(QWidget):
|
||||
container.set_inline(False)
|
||||
# TODO: Localize this and/or add preferences.
|
||||
date = dt.strptime(field.value, "%Y-%m-%d %H:%M:%S")
|
||||
title = f"{field.type.name} (Date)"
|
||||
title = f"{field.type.name} (Date)" # TODO translate
|
||||
inner_container = TextWidget(title, date.strftime("%D - %r"))
|
||||
container.set_inner_widget(inner_container)
|
||||
except Exception:
|
||||
container.set_title(field.type.name)
|
||||
# container.set_editable(False)
|
||||
container.set_inline(False)
|
||||
title = f"{field.type.name} (Date) (Unknown Format)"
|
||||
title = f"{field.type.name} (Date) (Unknown Format)" # TODO translate
|
||||
inner_container = TextWidget(title, str(field.value))
|
||||
container.set_inner_widget(inner_container)
|
||||
|
||||
@@ -1029,15 +1035,15 @@ class PreviewPanel(QWidget):
|
||||
)
|
||||
)
|
||||
else:
|
||||
text = "<i>Mixed Data</i>"
|
||||
title = f"{field.type.name} (Wacky Date)"
|
||||
text = "<i>Mixed Data</i>" # TODO translate
|
||||
title = f"{field.type.name} (Wacky Date)" # TODO translate
|
||||
inner_container = TextWidget(title, text)
|
||||
container.set_inner_widget(inner_container)
|
||||
else:
|
||||
logger.warning("write_container - unknown field", field=field)
|
||||
container.set_title(field.type.name)
|
||||
container.set_inline(False)
|
||||
title = f"{field.type.name} (Unknown Field Type)"
|
||||
title = f"{field.type.name} (Unknown Field Type)" # TODO translate
|
||||
inner_container = TextWidget(title, field.type.name)
|
||||
container.set_inner_widget(inner_container)
|
||||
container.set_remove_callback(
|
||||
@@ -1090,10 +1096,12 @@ class PreviewPanel(QWidget):
|
||||
def remove_message_box(self, prompt: str, callback: Callable) -> None:
|
||||
remove_mb = QMessageBox()
|
||||
remove_mb.setText(prompt)
|
||||
remove_mb.setWindowTitle("Remove Field")
|
||||
remove_mb.setWindowTitle("Remove Field") # TODO translate
|
||||
remove_mb.setIcon(QMessageBox.Icon.Warning)
|
||||
cancel_button = remove_mb.addButton("&Cancel", QMessageBox.ButtonRole.DestructiveRole)
|
||||
remove_mb.addButton("&Remove", QMessageBox.ButtonRole.RejectRole)
|
||||
cancel_button = remove_mb.addButton(
|
||||
Translations["generic.cancel_alt"], QMessageBox.ButtonRole.DestructiveRole
|
||||
)
|
||||
remove_mb.addButton("&Remove", QMessageBox.ButtonRole.RejectRole) # TODO translate
|
||||
# remove_mb.setStandardButtons(QMessageBox.StandardButton.Cancel)
|
||||
remove_mb.setDefaultButton(cancel_button)
|
||||
remove_mb.setEscapeButton(cancel_button)
|
||||
|
||||
@@ -15,8 +15,9 @@ class ProgressWidget(QWidget):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
window_title: str,
|
||||
label_text: str,
|
||||
*,
|
||||
window_title: str = "",
|
||||
label_text: str = "",
|
||||
cancel_button_text: Optional[str],
|
||||
minimum: int,
|
||||
maximum: int,
|
||||
|
||||
@@ -20,6 +20,7 @@ from PySide6.QtWidgets import (
|
||||
from src.core.library import Tag
|
||||
from src.core.library.alchemy.enums import TagColor
|
||||
from src.core.palette import ColorType, get_tag_color
|
||||
from src.qt.translations import Translations
|
||||
|
||||
|
||||
class TagAliasWidget(QWidget):
|
||||
@@ -126,17 +127,20 @@ class TagWidget(QWidget):
|
||||
self.bg_button.setFlat(True)
|
||||
self.bg_button.setText(tag.name)
|
||||
if has_edit:
|
||||
edit_action = QAction("Edit", self)
|
||||
edit_action = QAction(self)
|
||||
Translations.translate_qobject(edit_action, "generic.edit")
|
||||
edit_action.triggered.connect(on_edit_callback)
|
||||
edit_action.triggered.connect(self.on_edit.emit)
|
||||
self.bg_button.addAction(edit_action)
|
||||
# if on_click_callback:
|
||||
self.bg_button.setContextMenuPolicy(Qt.ContextMenuPolicy.ActionsContextMenu)
|
||||
|
||||
search_for_tag_action = QAction("Search for Tag", self)
|
||||
search_for_tag_action = QAction(self)
|
||||
Translations.translate_qobject(search_for_tag_action, "tag.search_for_tag")
|
||||
search_for_tag_action.triggered.connect(self.on_click.emit)
|
||||
self.bg_button.addAction(search_for_tag_action)
|
||||
add_to_search_action = QAction("Add to Search", self)
|
||||
add_to_search_action = QAction(self)
|
||||
Translations.translate_qobject(add_to_search_action, "tag.add_to_search")
|
||||
self.bg_button.addAction(add_to_search_action)
|
||||
|
||||
self.inner_layout = QHBoxLayout()
|
||||
|
||||
@@ -16,6 +16,7 @@ from src.core.library.alchemy.fields import TagBoxField
|
||||
from src.qt.flowlayout import FlowLayout
|
||||
from src.qt.modals.build_tag import BuildTagPanel
|
||||
from src.qt.modals.tag_search import TagSearchPanel
|
||||
from src.qt.translations import Translations
|
||||
from src.qt.widgets.fields import FieldWidget
|
||||
from src.qt.widgets.panel import PanelModal
|
||||
from src.qt.widgets.tag import TagWidget
|
||||
@@ -75,7 +76,8 @@ class TagBoxWidget(FieldWidget):
|
||||
)
|
||||
tsp = TagSearchPanel(self.driver.lib)
|
||||
tsp.tag_chosen.connect(lambda x: self.add_tag_callback(x))
|
||||
self.add_modal = PanelModal(tsp, title, "Add Tags")
|
||||
self.add_modal = PanelModal(tsp, title)
|
||||
Translations.translate_with_setter(self.add_modal.setWindowTitle, "tag.add.plural")
|
||||
self.add_button.clicked.connect(
|
||||
lambda: (
|
||||
tsp.update_tags(),
|
||||
@@ -130,11 +132,11 @@ class TagBoxWidget(FieldWidget):
|
||||
|
||||
self.edit_modal = PanelModal(
|
||||
build_tag_panel,
|
||||
tag.name, # TODO - display name including subtags
|
||||
"Edit Tag",
|
||||
title=tag.name, # TODO - display name including subtags
|
||||
done_callback=self.driver.preview_panel.update_widgets,
|
||||
has_save=True,
|
||||
)
|
||||
Translations.translate_with_setter(self.edit_modal.setWindowTitle, "tag.edit")
|
||||
# TODO - this was update_tag()
|
||||
self.edit_modal.saved.connect(
|
||||
lambda: self.driver.lib.update_tag(
|
||||
|
||||
@@ -12,6 +12,7 @@ from pathlib import Path
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
import pillow_jxl # noqa: F401
|
||||
import rawpy
|
||||
import structlog
|
||||
from mutagen import MutagenError, flac, id3, mp4
|
||||
|
||||
@@ -31,6 +31,7 @@ from PySide6.QtWidgets import QGraphicsScene, QGraphicsView
|
||||
from src.core.enums import SettingItems
|
||||
from src.qt.helpers.file_opener import FileOpenerHelper
|
||||
from src.qt.platform_strings import PlatformStrings
|
||||
from src.qt.translations import Translations
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from src.qt.ts_qt import QtDriver
|
||||
@@ -115,7 +116,8 @@ class VideoPlayer(QGraphicsView):
|
||||
|
||||
self.setContextMenuPolicy(Qt.ContextMenuPolicy.ActionsContextMenu)
|
||||
self.opener = FileOpenerHelper(filepath=self.filepath)
|
||||
autoplay_action = QAction("Autoplay", self)
|
||||
autoplay_action = QAction(self)
|
||||
Translations.translate_qobject(autoplay_action, "media_player.autoplay")
|
||||
autoplay_action.setCheckable(True)
|
||||
self.addAction(autoplay_action)
|
||||
autoplay_action.setChecked(
|
||||
@@ -124,7 +126,8 @@ class VideoPlayer(QGraphicsView):
|
||||
autoplay_action.triggered.connect(lambda: self.toggle_autoplay())
|
||||
self.autoplay = autoplay_action
|
||||
|
||||
open_file_action = QAction("Open file", self)
|
||||
open_file_action = QAction(self)
|
||||
Translations.translate_qobject(open_file_action, "file.open_file")
|
||||
open_file_action.triggered.connect(self.opener.open_file)
|
||||
|
||||
open_explorer_action = QAction(PlatformStrings.open_file_str, self)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from src.core.library.alchemy.models import Tag
|
||||
from src.qt.modals.build_tag import BuildTagPanel
|
||||
from src.qt.translations import Translations
|
||||
|
||||
|
||||
def test_build_tag_panel_add_sub_tag_callback(library, generate_tag):
|
||||
@@ -155,4 +156,4 @@ def test_build_tag_panel_build_tag(library):
|
||||
tag: Tag = panel.build_tag()
|
||||
|
||||
assert tag
|
||||
assert tag.name == "New Tag"
|
||||
assert tag.name == Translations["tag.new"]
|
||||
|
||||
@@ -116,6 +116,20 @@ def test_parentheses(search_library: Library, query: str, count: int):
|
||||
verify_count(search_library, query, count)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
["query", "count"],
|
||||
[
|
||||
("ellipse", 17),
|
||||
("yellow", 15),
|
||||
("color", 24),
|
||||
("shape", 24),
|
||||
("yellow not green", 10),
|
||||
],
|
||||
)
|
||||
def test_parent_tags(search_library: Library, query: str, count: int):
|
||||
verify_count(search_library, query, count)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"invalid_query", ["asd AND", "asd AND AND", "tag:(", "(asd", "asd[]", "asd]", ":", "tag: :"]
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user