mirror of
https://github.com/TagStudioDev/TagStudio.git
synced 2026-01-31 23:29:10 +00:00
chore: merge main into pyright-alchemy
This commit is contained in:
@@ -77,7 +77,7 @@ A detailed written specification for the TagStudio tag and/or library format. In
|
||||
- [ ] Lightbox View :material-chevron-triple-up:{ .priority-high title="High Priority" }
|
||||
- Similar to List View in concept, but displays one large preview that can cycle back/forth between entries.
|
||||
- [ ] Smaller thumbnails of immediate adjacent entries below :material-chevron-double-up:{ .priority-med title="Medium Priority" }
|
||||
- [ ] Library Statistics Screen :material-chevron-double-up:{ .priority-med title="Medium Priority" } **[v9.5.4]**
|
||||
- [x] Library Statistics Screen :material-chevron-double-up:{ .priority-med title="Medium Priority" } **[v9.5.4]**
|
||||
- [ ] Unified Library Health/Cleanup Screen :material-chevron-double-up:{ .priority-med title="Medium Priority" } **[v9.5.4]**
|
||||
- [x] Fix Unlinked Entries
|
||||
- [x] Fix Duplicate Files
|
||||
@@ -125,7 +125,7 @@ A detailed written specification for the TagStudio tag and/or library format. In
|
||||
- [x] Language
|
||||
- [x] Date and Time Format
|
||||
- [x] Theme
|
||||
- [ ] Thumbnail Generation :material-chevron-double-up:{ .priority-med title="Medium Priority" } **[v9.5.4]**
|
||||
- [x] Thumbnail Generation :material-chevron-double-up:{ .priority-med title="Medium Priority" } **[v9.5.4]**
|
||||
- [x] Configurable Page Size
|
||||
- [ ] Library Settings :material-chevron-triple-up:{ .priority-high title="High Priority" }
|
||||
- [ ] Stored in `.TagStudio` folder :material-chevron-triple-up:{ .priority-high title="High Priority" }
|
||||
@@ -147,6 +147,7 @@ Some form of official plugin support for TagStudio, likely with its own API that
|
||||
- [x] Per-Library Tags
|
||||
- [ ] Global Tags :material-chevron-triple-up:{ .priority-high title="High Priority" }
|
||||
- [ ] Multiple Root Directories :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.0]**
|
||||
- [ ] Ability to store TagStudio library folder separate from library files :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.0]**
|
||||
- [ ] Automatic Entry Relinking :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.7.0]**
|
||||
- [ ] Detect Renames :material-chevron-triple-up:{ .priority-high title="High Priority" }
|
||||
- [ ] Detect Moves :material-chevron-triple-up:{ .priority-high title="High Priority" }
|
||||
@@ -168,7 +169,7 @@ Library representations of files or file-like objects.
|
||||
- [x] Text Boxes
|
||||
- [x] Datetimes **[v9.5.4]**
|
||||
- [ ] User-Titled Fields :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.0]**
|
||||
- [ ] Removal of Deprecated Fields :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.0]**
|
||||
- [ ] Removal of Deprecated Fields :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.0]**
|
||||
- [ ] Entry Groups :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.7.0]**
|
||||
- [ ] Non-exclusive; Entries can be in multiple groups :material-chevron-triple-up:{ .priority-high title="High Priority" }
|
||||
- [ ] Ability to number entries within group :material-chevron-triple-up:{ .priority-high title="High Priority" }
|
||||
|
||||
@@ -71,7 +71,9 @@ As of version 9.5, libraries are saved automatically as you go. To save a backup
|
||||
|
||||
There are a handful of launch arguments you can pass to TagStudio via the command line or a desktop shortcut.
|
||||
|
||||
| Argument | Short | Description |
|
||||
| ---------------------- | ----- | ---------------------------------------------------- |
|
||||
| `--open <path>` | `-o` | Path to a TagStudio Library folder to open on start. |
|
||||
| `--config-file <path>` | `-c` | Path to the TagStudio config file to load. |
|
||||
| Argument | Short | Description |
|
||||
| ------------------------ | ----- | ------------------------------------------------------ |
|
||||
| `--cache-file <path>` | `-c` | Path to a TagStudio .ini or .plist cache file to use. |
|
||||
| `--open <path>` | `-o` | Path to a TagStudio Library folder to open on start. |
|
||||
| `--settings-file <path>` | `-s` | Path to a TagStudio .toml global settings file to use. |
|
||||
| `--version` | `-v` | Displays TagStudio version information. |
|
||||
|
||||
@@ -74,6 +74,7 @@ python3Packages.buildPythonApplication {
|
||||
pythonRelaxDeps = [
|
||||
"numpy"
|
||||
"pillow"
|
||||
"pillow-avif-plugin"
|
||||
"pillow-heif"
|
||||
"pillow-jxl-plugin"
|
||||
"pyside6"
|
||||
@@ -93,6 +94,7 @@ python3Packages.buildPythonApplication {
|
||||
numpy
|
||||
opencv-python
|
||||
pillow
|
||||
pillow-avif-plugin
|
||||
pillow-heif
|
||||
pydantic
|
||||
pydub
|
||||
@@ -108,34 +110,24 @@ python3Packages.buildPythonApplication {
|
||||
]
|
||||
++ lib.optional withJXLSupport pillow-jxl-plugin;
|
||||
|
||||
# These tests require modifications to a library, which does not work
|
||||
# in a read-only environment.
|
||||
disabledTests = [
|
||||
# These tests require modifications to a library, which does not work
|
||||
# in a read-only environment.
|
||||
"test_build_tag_panel_add_alias_callback"
|
||||
"test_build_tag_panel_add_aliases"
|
||||
"test_build_tag_panel_add_sub_tag_callback"
|
||||
"test_build_tag_panel_build_tag"
|
||||
"test_build_tag_panel_remove_alias_callback"
|
||||
"test_build_tag_panel_remove_subtag_callback"
|
||||
"test_build_tag_panel_set_aliases"
|
||||
"test_build_tag_panel_set_parent_tags"
|
||||
"test_build_tag_panel_set_tag"
|
||||
"test_badge_visual_state"
|
||||
"test_browsing_state_update"
|
||||
"test_flow_layout_happy_path"
|
||||
"test_json_migration"
|
||||
"test_library_migrations"
|
||||
|
||||
"test_add_same_tag_to_selection_single"
|
||||
"test_add_tag_to_selection_multiple"
|
||||
"test_add_tag_to_selection_single"
|
||||
"test_custom_tag_category"
|
||||
"test_file_path_display"
|
||||
"test_meta_tag_category"
|
||||
"test_update_selection_empty"
|
||||
"test_update_selection_empty"
|
||||
"test_update_selection_multiple"
|
||||
"test_update_selection_single"
|
||||
|
||||
# This test requires modification of a configuration file.
|
||||
"test_filepath_setting"
|
||||
"test_update_tags"
|
||||
];
|
||||
disabledTestPaths = [
|
||||
"tests/qt/test_build_tag_panel.py"
|
||||
"tests/qt/test_field_containers.py"
|
||||
"tests/qt/test_file_path_options.py"
|
||||
"tests/qt/test_preview_panel.py"
|
||||
"tests/qt/test_tag_panel.py"
|
||||
"tests/qt/test_tag_search_panel.py"
|
||||
"tests/test_library.py"
|
||||
];
|
||||
|
||||
meta = {
|
||||
|
||||
@@ -5,7 +5,7 @@ build-backend = "hatchling.build"
|
||||
[project]
|
||||
name = "TagStudio"
|
||||
description = "A User-Focused Photo & File Management System."
|
||||
version = "9.5.3"
|
||||
version = "9.5.4"
|
||||
license = "GPL-3.0-only"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12,<3.13"
|
||||
@@ -16,7 +16,8 @@ dependencies = [
|
||||
"mutagen~=1.47",
|
||||
"numpy~=2.2",
|
||||
"opencv_python~=4.11",
|
||||
"Pillow>=10.2,<=12.0",
|
||||
"Pillow>=10.2,<=11",
|
||||
"pillow-avif-plugin~=1.5",
|
||||
"pillow-heif~=0.22",
|
||||
"pillow-jxl-plugin~=1.3",
|
||||
"pydantic~=2.10",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
VERSION: str = "9.5.3" # Major.Minor.Patch
|
||||
VERSION: str = "9.5.4" # Major.Minor.Patch
|
||||
VERSION_BRANCH: str = "" # Usually "" or "Pre-Release"
|
||||
|
||||
# The folder & file names where TagStudio keeps its data relative to a library.
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import platform
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from enum import Enum, IntEnum, StrEnum
|
||||
from pathlib import Path
|
||||
from typing import override
|
||||
|
||||
@@ -13,45 +13,53 @@ from pydantic import BaseModel, Field
|
||||
|
||||
from tagstudio.core.enums import ShowFilepathOption, TagClickActionOption
|
||||
|
||||
if platform.system() == "Windows":
|
||||
DEFAULT_GLOBAL_SETTINGS_PATH = (
|
||||
Path.home() / "Appdata" / "Roaming" / "TagStudio" / "settings.toml"
|
||||
)
|
||||
else:
|
||||
DEFAULT_GLOBAL_SETTINGS_PATH = Path.home() / ".config" / "TagStudio" / "settings.toml"
|
||||
DEFAULT_GLOBAL_SETTINGS_PATH = (
|
||||
Path.home() / "Appdata" / "Roaming" / "TagStudio" / "settings.toml"
|
||||
if platform.system() == "Windows"
|
||||
else Path.home() / ".config" / "TagStudio" / "settings.toml"
|
||||
)
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
class TomlEnumEncoder(toml.TomlEncoder):
|
||||
@override
|
||||
def dump_value(self, v):
|
||||
def dump_value(self, v): # pyright: ignore[reportMissingParameterType]
|
||||
if isinstance(v, Enum):
|
||||
return super().dump_value(v.value)
|
||||
return super().dump_value(v)
|
||||
|
||||
|
||||
class Theme(Enum):
|
||||
class Theme(IntEnum):
|
||||
DARK = 0
|
||||
LIGHT = 1
|
||||
SYSTEM = 2
|
||||
DEFAULT = SYSTEM
|
||||
|
||||
|
||||
class Splash(StrEnum):
|
||||
DEFAULT = "default"
|
||||
RANDOM = "random"
|
||||
CLASSIC = "classic"
|
||||
GOO_GEARS = "goo_gears"
|
||||
NINETY_FIVE = "95"
|
||||
|
||||
|
||||
# NOTE: pydantic also has a BaseSettings class (from pydantic-settings) that allows any settings
|
||||
# properties to be overwritten with environment variables. as tagstudio is not currently using
|
||||
# environment variables, i did not base it on that, but that may be useful in the future.
|
||||
# properties to be overwritten with environment variables. As TagStudio is not currently using
|
||||
# environment variables, this was not based on that, but that may be useful in the future.
|
||||
class GlobalSettings(BaseModel):
|
||||
language: str = Field(default="en")
|
||||
open_last_loaded_on_startup: bool = Field(default=True)
|
||||
generate_thumbs: bool = Field(default=False)
|
||||
generate_thumbs: bool = Field(default=True)
|
||||
autoplay: bool = Field(default=True)
|
||||
loop: bool = Field(default=True)
|
||||
show_filenames_in_grid: bool = Field(default=True)
|
||||
page_size: int = Field(default=100)
|
||||
show_filepath: ShowFilepathOption = Field(default=ShowFilepathOption.DEFAULT)
|
||||
theme: Theme = Field(default=Theme.SYSTEM)
|
||||
tag_click_action: TagClickActionOption = Field(default=TagClickActionOption.DEFAULT)
|
||||
theme: Theme = Field(default=Theme.SYSTEM)
|
||||
splash: Splash = Field(default=Splash.DEFAULT)
|
||||
|
||||
date_format: str = Field(default="%x")
|
||||
hour_format: bool = Field(default=True)
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
|
||||
from sqlalchemy import text
|
||||
|
||||
SQL_FILENAME: str = "ts_library.sqlite"
|
||||
JSON_FILENAME: str = "ts_library.json"
|
||||
|
||||
DB_VERSION_LEGACY_KEY: str = "DB_VERSION"
|
||||
DB_VERSION_CURRENT_KEY: str = "CURRENT"
|
||||
DB_VERSION_INITIAL_KEY: str = "INITIAL"
|
||||
|
||||
@@ -87,6 +87,8 @@ from tagstudio.core.library.alchemy.constants import (
|
||||
DB_VERSION_CURRENT_KEY,
|
||||
DB_VERSION_INITIAL_KEY,
|
||||
DB_VERSION_LEGACY_KEY,
|
||||
JSON_FILENAME,
|
||||
SQL_FILENAME,
|
||||
TAG_CHILDREN_ID_QUERY,
|
||||
TAG_CHILDREN_QUERY,
|
||||
)
|
||||
@@ -194,8 +196,11 @@ class Library:
|
||||
folder: Folder | None = None
|
||||
included_files: set[Path] = set()
|
||||
|
||||
SQL_FILENAME: str = "ts_library.sqlite"
|
||||
JSON_FILENAME: str = "ts_library.json"
|
||||
def __init__(self) -> None:
|
||||
self.dupe_entries_count: int = -1 # NOTE: For internal management.
|
||||
self.dupe_files_count: int = -1
|
||||
self.ignored_entries_count: int = -1
|
||||
self.unlinked_entries_count: int = -1
|
||||
|
||||
def close(self):
|
||||
if self.engine:
|
||||
@@ -205,6 +210,11 @@ class Library:
|
||||
self.folder = None
|
||||
self.included_files = set()
|
||||
|
||||
self.dupe_entries_count = -1
|
||||
self.dupe_files_count = -1
|
||||
self.ignored_entries_count = -1
|
||||
self.unlinked_entries_count = -1
|
||||
|
||||
def migrate_json_to_sqlite(self, json_lib: JsonLibrary):
|
||||
"""Migrate JSON library data to the SQLite database."""
|
||||
logger.info("Starting Library Conversion...")
|
||||
@@ -321,10 +331,10 @@ class Library:
|
||||
is_new = True
|
||||
return self.open_sqlite_library(library_dir, is_new)
|
||||
else:
|
||||
self.storage_path = library_dir / TS_FOLDER_NAME / self.SQL_FILENAME
|
||||
self.storage_path = library_dir / TS_FOLDER_NAME / SQL_FILENAME
|
||||
assert isinstance(self.storage_path, Path)
|
||||
if self.verify_ts_folder(library_dir) and (is_new := not self.storage_path.exists()):
|
||||
json_path = library_dir / TS_FOLDER_NAME / self.JSON_FILENAME
|
||||
json_path = library_dir / TS_FOLDER_NAME / JSON_FILENAME
|
||||
if json_path.exists():
|
||||
return LibraryStatus(
|
||||
success=False,
|
||||
@@ -1403,8 +1413,13 @@ class Library:
|
||||
|
||||
def add_tags_to_entries(
|
||||
self, entry_ids: int | list[int], tag_ids: int | list[int] | set[int]
|
||||
) -> bool:
|
||||
"""Add one or more tags to one or more entries."""
|
||||
) -> int:
|
||||
"""Add one or more tags to one or more entries.
|
||||
|
||||
Returns:
|
||||
The total number of tags added across all entries.
|
||||
"""
|
||||
total_added: int = 0
|
||||
logger.info(
|
||||
"[Library][add_tags_to_entries]",
|
||||
entry_ids=entry_ids,
|
||||
@@ -1418,16 +1433,12 @@ class Library:
|
||||
for entry_id in entry_ids_:
|
||||
try:
|
||||
session.add(TagEntry(tag_id=tag_id, entry_id=entry_id))
|
||||
session.flush()
|
||||
total_added += 1
|
||||
session.commit()
|
||||
except IntegrityError:
|
||||
session.rollback()
|
||||
try:
|
||||
session.commit()
|
||||
except IntegrityError as e:
|
||||
logger.warning("[Library][add_tags_to_entries]", warning=e)
|
||||
session.rollback()
|
||||
return False
|
||||
return True
|
||||
|
||||
return total_added
|
||||
|
||||
def remove_tags_from_entries(
|
||||
self, entry_ids: int | list[int], tag_ids: int | list[int] | set[int]
|
||||
@@ -1493,7 +1504,7 @@ class Library:
|
||||
target_path = self.library_dir / TS_FOLDER_NAME / BACKUP_FOLDER_NAME / filename
|
||||
|
||||
shutil.copy2(
|
||||
self.library_dir / TS_FOLDER_NAME / self.SQL_FILENAME,
|
||||
self.library_dir / TS_FOLDER_NAME / SQL_FILENAME,
|
||||
target_path,
|
||||
)
|
||||
|
||||
@@ -1872,12 +1883,9 @@ class Library:
|
||||
if not result:
|
||||
success = False
|
||||
tag_ids = [tag.id for tag in from_entry.tags]
|
||||
add_result = self.add_tags_to_entries(into_entry.id, tag_ids)
|
||||
self.add_tags_to_entries(into_entry.id, tag_ids)
|
||||
self.remove_entries([from_entry.id])
|
||||
|
||||
if not add_result:
|
||||
success = False
|
||||
|
||||
return success
|
||||
|
||||
@property
|
||||
|
||||
@@ -9,7 +9,7 @@ from pathlib import Path
|
||||
|
||||
from tagstudio.core.constants import TS_FOLDER_NAME
|
||||
from tagstudio.core.library.alchemy.library import Entry, FieldID, Library
|
||||
from tagstudio.core.utils.missing_files import logger
|
||||
from tagstudio.core.utils.unlinked_registry import logger
|
||||
|
||||
|
||||
class TagStudioCore:
|
||||
|
||||
50
src/tagstudio/core/utils/ignored_registry.py
Normal file
50
src/tagstudio/core/utils/ignored_registry.py
Normal file
@@ -0,0 +1,50 @@
|
||||
# Copyright (C) 2025
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
|
||||
from collections.abc import Iterator
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
|
||||
import structlog
|
||||
|
||||
from tagstudio.core.library.alchemy.library import Entry, Library
|
||||
from tagstudio.core.library.ignore import Ignore
|
||||
from tagstudio.core.utils.types import unwrap
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class IgnoredRegistry:
|
||||
"""State tracker for ignored entries."""
|
||||
|
||||
lib: Library
|
||||
ignored_entries: list[Entry] = field(default_factory=list)
|
||||
|
||||
@property
|
||||
def ignored_count(self) -> int:
|
||||
return len(self.ignored_entries)
|
||||
|
||||
def reset(self):
|
||||
self.ignored_entries.clear()
|
||||
|
||||
def refresh_ignored_entries(self) -> Iterator[int]:
|
||||
"""Track the number of entries that would otherwise be ignored by the current rules."""
|
||||
logger.info("[IgnoredRegistry] Refreshing ignored entries...")
|
||||
|
||||
self.ignored_entries = []
|
||||
library_dir: Path = unwrap(self.lib.library_dir)
|
||||
|
||||
for i, entry in enumerate(self.lib.all_entries()):
|
||||
if not Ignore.compiled_patterns:
|
||||
# If the compiled_patterns has malfunctioned, don't consider that a false positive
|
||||
yield i
|
||||
elif Ignore.compiled_patterns.match(library_dir / entry.path):
|
||||
self.ignored_entries.append(entry)
|
||||
yield i
|
||||
|
||||
def remove_ignored_entries(self) -> None:
|
||||
self.lib.remove_entries(list(map(lambda ignored: ignored.id, self.ignored_entries)))
|
||||
self.ignored_entries = []
|
||||
@@ -1,3 +1,8 @@
|
||||
# Copyright (C) 2025
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
|
||||
import shutil
|
||||
from collections.abc import Iterator
|
||||
from dataclasses import dataclass, field
|
||||
@@ -10,7 +15,7 @@ from wcmatch import pathlib
|
||||
|
||||
from tagstudio.core.library.alchemy.library import Entry, Library
|
||||
from tagstudio.core.library.ignore import PATH_GLOB_FLAGS, Ignore, ignore_to_glob
|
||||
from tagstudio.qt.helpers.silent_popen import silent_run
|
||||
from tagstudio.qt.helpers.silent_popen import silent_run # pyright: ignore
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
@@ -13,34 +13,37 @@ logger = structlog.get_logger()
|
||||
|
||||
|
||||
@dataclass
|
||||
class MissingRegistry:
|
||||
"""State tracker for unlinked and moved files."""
|
||||
class UnlinkedRegistry:
|
||||
"""State tracker for unlinked entries."""
|
||||
|
||||
library: Library
|
||||
lib: Library
|
||||
files_fixed_count: int = 0
|
||||
missing_file_entries: list[Entry] = field(default_factory=list)
|
||||
unlinked_entries: list[Entry] = field(default_factory=list)
|
||||
|
||||
@property
|
||||
def missing_file_entries_count(self) -> int:
|
||||
return len(self.missing_file_entries)
|
||||
def unlinked_entries_count(self) -> int:
|
||||
return len(self.unlinked_entries)
|
||||
|
||||
def refresh_missing_files(self) -> Iterator[int]:
|
||||
def reset(self):
|
||||
self.unlinked_entries.clear()
|
||||
|
||||
def refresh_unlinked_files(self) -> Iterator[int]:
|
||||
"""Track the number of entries that point to an invalid filepath."""
|
||||
logger.info("[refresh_missing_files] Refreshing missing files...")
|
||||
logger.info("[UnlinkedRegistry] Refreshing unlinked files...")
|
||||
|
||||
self.missing_file_entries = []
|
||||
for i, entry in enumerate(self.library.all_entries()):
|
||||
full_path = unwrap(self.library.library_dir) / entry.path
|
||||
self.unlinked_entries = []
|
||||
for i, entry in enumerate(self.lib.all_entries()):
|
||||
full_path = unwrap(self.lib.library_dir) / entry.path
|
||||
if not full_path.exists() or not full_path.is_file():
|
||||
self.missing_file_entries.append(entry)
|
||||
self.unlinked_entries.append(entry)
|
||||
yield i
|
||||
|
||||
def match_missing_file_entry(self, match_entry: Entry) -> list[Path]:
|
||||
def match_unlinked_file_entry(self, match_entry: Entry) -> list[Path]:
|
||||
"""Try and match unlinked file entries with matching results in the library directory.
|
||||
|
||||
Works if files were just moved to different subfolders and don't have duplicate names.
|
||||
"""
|
||||
library_dir = unwrap(self.library.library_dir)
|
||||
library_dir = unwrap(self.lib.library_dir)
|
||||
matches: list[Path] = []
|
||||
|
||||
ignore_patterns = Ignore.get_patterns(library_dir)
|
||||
@@ -55,26 +58,26 @@ class MissingRegistry:
|
||||
new_path = Path(path).relative_to(library_dir)
|
||||
matches.append(new_path)
|
||||
|
||||
logger.info("[MissingRegistry] Matches", matches=matches)
|
||||
logger.info("[UnlinkedRegistry] Matches", matches=matches)
|
||||
return matches
|
||||
|
||||
def fix_unlinked_entries(self) -> Iterator[int]:
|
||||
"""Attempt to fix unlinked file entries by finding a match in the library directory."""
|
||||
self.files_fixed_count = 0
|
||||
matched_entries: list[Entry] = []
|
||||
for i, entry in enumerate(self.missing_file_entries):
|
||||
item_matches = self.match_missing_file_entry(entry)
|
||||
for i, entry in enumerate(self.unlinked_entries):
|
||||
item_matches = self.match_unlinked_file_entry(entry)
|
||||
if len(item_matches) == 1:
|
||||
logger.info(
|
||||
"[fix_unlinked_entries]",
|
||||
"[UnlinkedRegistry]",
|
||||
entry=entry.path.as_posix(),
|
||||
item_matches=item_matches[0].as_posix(),
|
||||
)
|
||||
if not self.library.update_entry_path(entry.id, item_matches[0]):
|
||||
if not self.lib.update_entry_path(entry.id, item_matches[0]):
|
||||
try:
|
||||
match = unwrap(self.library.get_entry_full_by_path(item_matches[0]))
|
||||
entry_full = unwrap(self.library.get_entry_full(entry.id))
|
||||
self.library.merge_entries(entry_full, match)
|
||||
match = unwrap(self.lib.get_entry_full_by_path(item_matches[0]))
|
||||
entry_full = unwrap(self.lib.get_entry_full(entry.id))
|
||||
self.lib.merge_entries(entry_full, match)
|
||||
except AttributeError:
|
||||
continue
|
||||
self.files_fixed_count += 1
|
||||
@@ -82,11 +85,8 @@ class MissingRegistry:
|
||||
yield i
|
||||
|
||||
for entry in matched_entries:
|
||||
self.missing_file_entries.remove(entry)
|
||||
self.unlinked_entries.remove(entry)
|
||||
|
||||
def execute_deletion(self) -> None:
|
||||
self.library.remove_entries(
|
||||
list(map(lambda missing: missing.id, self.missing_file_entries))
|
||||
)
|
||||
|
||||
self.missing_file_entries = []
|
||||
def remove_unlinked_entries(self) -> None:
|
||||
self.lib.remove_entries(list(map(lambda unlinked: unlinked.id, self.unlinked_entries)))
|
||||
self.unlinked_entries = []
|
||||
94
src/tagstudio/qt/controller/fix_ignored_modal_controller.py
Normal file
94
src/tagstudio/qt/controller/fix_ignored_modal_controller.py
Normal file
@@ -0,0 +1,94 @@
|
||||
# Copyright (C) 2025 Travis Abendshien (CyanVoxel).
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
|
||||
from typing import TYPE_CHECKING, override
|
||||
|
||||
import structlog
|
||||
from PySide6 import QtGui
|
||||
|
||||
from tagstudio.core.library.alchemy.library import Library
|
||||
from tagstudio.core.utils.ignored_registry import IgnoredRegistry
|
||||
from tagstudio.qt.modals.remove_ignored_modal import RemoveIgnoredModal
|
||||
from tagstudio.qt.translations import Translations
|
||||
from tagstudio.qt.view.widgets.fix_ignored_modal_view import FixIgnoredEntriesModalView
|
||||
from tagstudio.qt.widgets.progress import ProgressWidget
|
||||
|
||||
# Only import for type checking/autocompletion, will not be imported at runtime.
|
||||
if TYPE_CHECKING:
|
||||
from tagstudio.qt.ts_qt import QtDriver
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
class FixIgnoredEntriesModal(FixIgnoredEntriesModalView):
|
||||
def __init__(self, library: "Library", driver: "QtDriver"):
|
||||
super().__init__(library, driver)
|
||||
self.tracker = IgnoredRegistry(self.lib)
|
||||
|
||||
self.remove_modal = RemoveIgnoredModal(self.driver, self.tracker)
|
||||
self.remove_modal.done.connect(
|
||||
lambda: (
|
||||
self.update_ignored_count(),
|
||||
self.driver.update_browsing_state(),
|
||||
self.driver.library_info_window.update_cleanup(),
|
||||
self.refresh_ignored(),
|
||||
)
|
||||
)
|
||||
|
||||
self.refresh_ignored_button.clicked.connect(self.refresh_ignored)
|
||||
self.remove_button.clicked.connect(self.remove_modal.show)
|
||||
self.done_button.clicked.connect(self.hide)
|
||||
|
||||
self.update_ignored_count()
|
||||
|
||||
def refresh_ignored(self):
|
||||
pw = ProgressWidget(
|
||||
cancel_button_text=None,
|
||||
minimum=0,
|
||||
maximum=self.lib.entries_count,
|
||||
)
|
||||
pw.setWindowTitle(Translations["library.scan_library.title"])
|
||||
pw.update_label(Translations["entries.ignored.scanning"])
|
||||
|
||||
def update_driver_widgets():
|
||||
if (
|
||||
hasattr(self.driver, "library_info_window")
|
||||
and self.driver.library_info_window.isVisible()
|
||||
):
|
||||
self.driver.library_info_window.update_cleanup()
|
||||
|
||||
pw.from_iterable_function(
|
||||
self.tracker.refresh_ignored_entries,
|
||||
None,
|
||||
self.set_ignored_count,
|
||||
self.update_ignored_count,
|
||||
self.remove_modal.refresh_list,
|
||||
update_driver_widgets,
|
||||
)
|
||||
|
||||
def set_ignored_count(self):
|
||||
"""Sets the ignored_entries_count in the Library to the tracker's value."""
|
||||
self.lib.ignored_entries_count = self.tracker.ignored_count
|
||||
|
||||
def update_ignored_count(self):
|
||||
"""Updates the UI to reflect the Library's current ignored_entries_count."""
|
||||
# Indicates that the library is new compared to the last update.
|
||||
# NOTE: Make sure set_ignored_count() is called before this!
|
||||
if self.tracker.ignored_count > 0 and self.lib.ignored_entries_count < 0:
|
||||
self.tracker.reset()
|
||||
|
||||
count: int = self.lib.ignored_entries_count
|
||||
|
||||
self.remove_button.setDisabled(count < 1)
|
||||
|
||||
count_text: str = Translations.format(
|
||||
"entries.ignored.ignored_count", count=count if count >= 0 else "—"
|
||||
)
|
||||
self.ignored_count_label.setText(f"<h3>{count_text}</h3>")
|
||||
|
||||
@override
|
||||
def showEvent(self, event: QtGui.QShowEvent) -> None: # type: ignore
|
||||
self.update_ignored_count()
|
||||
return super().showEvent(event)
|
||||
@@ -1,13 +1,24 @@
|
||||
# Copyright (C) 2025 Travis Abendshien (CyanVoxel).
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, override
|
||||
from warnings import catch_warnings
|
||||
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import structlog
|
||||
from humanfriendly import format_size # pyright: ignore[reportUnknownVariableType]
|
||||
from PySide6 import QtGui
|
||||
|
||||
from tagstudio.core.constants import BACKUP_FOLDER_NAME, TS_FOLDER_NAME
|
||||
from tagstudio.core.library.alchemy.constants import (
|
||||
DB_VERSION,
|
||||
DB_VERSION_CURRENT_KEY,
|
||||
JSON_FILENAME,
|
||||
)
|
||||
from tagstudio.core.library.alchemy.library import Library
|
||||
from tagstudio.core.utils.types import unwrap
|
||||
from tagstudio.qt.helpers import file_opener
|
||||
from tagstudio.qt.translations import Translations
|
||||
from tagstudio.qt.view.widgets.library_info_window_view import LibraryInfoWindowView
|
||||
|
||||
@@ -15,17 +26,33 @@ from tagstudio.qt.view.widgets.library_info_window_view import LibraryInfoWindow
|
||||
if TYPE_CHECKING:
|
||||
from tagstudio.qt.ts_qt import QtDriver
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
class LibraryInfoWindow(LibraryInfoWindowView):
|
||||
def __init__(self, library: "Library", driver: "QtDriver"):
|
||||
super().__init__(library, driver)
|
||||
|
||||
# Statistics Buttons
|
||||
self.manage_tags_button.clicked.connect(
|
||||
self.driver.main_window.menu_bar.tag_manager_action.trigger
|
||||
)
|
||||
self.manage_colors_button.clicked.connect(
|
||||
self.driver.main_window.menu_bar.color_manager_action.trigger
|
||||
)
|
||||
|
||||
# Cleanup Buttons
|
||||
self.fix_unlinked_entries.clicked.connect(
|
||||
self.driver.main_window.menu_bar.fix_unlinked_entries_action.trigger
|
||||
)
|
||||
self.fix_ignored_entries.clicked.connect(
|
||||
self.driver.main_window.menu_bar.fix_ignored_entries_action.trigger
|
||||
)
|
||||
self.fix_dupe_files.clicked.connect(
|
||||
self.driver.main_window.menu_bar.fix_dupe_files_action.trigger
|
||||
)
|
||||
|
||||
# General Buttons
|
||||
self.close_button.clicked.connect(lambda: self.close())
|
||||
|
||||
def update_title(self):
|
||||
@@ -47,6 +74,93 @@ class LibraryInfoWindow(LibraryInfoWindowView):
|
||||
|
||||
self.macros_count_label.setText("<b>1</b>") # TODO: Implement macros system
|
||||
|
||||
def showEvent(self, event: QtGui.QShowEvent): # noqa N802
|
||||
def update_cleanup(self):
|
||||
# Unlinked Entries
|
||||
unlinked_count: str = (
|
||||
str(self.lib.unlinked_entries_count) if self.lib.unlinked_entries_count >= 0 else "—"
|
||||
)
|
||||
self.unlinked_count_label.setText(f"<b>{unlinked_count}</b>")
|
||||
|
||||
# Ignored Entries
|
||||
ignored_count: str = (
|
||||
str(self.lib.ignored_entries_count) if self.lib.ignored_entries_count >= 0 else "—"
|
||||
)
|
||||
self.ignored_count_label.setText(f"<b>{ignored_count}</b>")
|
||||
|
||||
# Duplicate Files
|
||||
dupe_files_count: str = (
|
||||
str(self.lib.dupe_files_count) if self.lib.dupe_files_count >= 0 else "—"
|
||||
)
|
||||
self.dupe_files_count_label.setText(f"<b>{dupe_files_count}</b>")
|
||||
|
||||
# Legacy JSON Library Present
|
||||
json_library_text: str = (
|
||||
Translations["generic.yes"]
|
||||
if self.__is_json_library_present
|
||||
else Translations["generic.no"]
|
||||
)
|
||||
self.legacy_json_status_label.setText(f"<b>{json_library_text}</b>")
|
||||
|
||||
# Backups
|
||||
self.backups_count_label.setText(
|
||||
f"<b>{self.__backups_count}</b> ({format_size(self.__backups_size)})"
|
||||
)
|
||||
|
||||
# Buttons
|
||||
with catch_warnings(record=True):
|
||||
self.view_legacy_json_file.clicked.disconnect()
|
||||
self.open_backups_folder.clicked.disconnect()
|
||||
|
||||
if self.__is_json_library_present:
|
||||
self.view_legacy_json_file.setEnabled(True)
|
||||
self.view_legacy_json_file.clicked.connect(
|
||||
lambda: file_opener.open_file(
|
||||
unwrap(self.lib.library_dir) / TS_FOLDER_NAME / JSON_FILENAME, file_manager=True
|
||||
)
|
||||
)
|
||||
else:
|
||||
self.view_legacy_json_file.setEnabled(False)
|
||||
|
||||
self.open_backups_folder.clicked.connect(
|
||||
lambda: file_opener.open_file(
|
||||
unwrap(self.lib.library_dir) / TS_FOLDER_NAME / BACKUP_FOLDER_NAME
|
||||
)
|
||||
)
|
||||
|
||||
def update_version(self):
|
||||
version_text: str = f"<b>{self.lib.get_version(DB_VERSION_CURRENT_KEY)}</b> / {DB_VERSION}"
|
||||
self.version_label.setText(
|
||||
Translations.format("library_info.version", version=version_text)
|
||||
)
|
||||
|
||||
def refresh(self):
|
||||
self.update_title()
|
||||
self.update_stats()
|
||||
self.update_cleanup()
|
||||
self.update_version()
|
||||
|
||||
@property
|
||||
def __is_json_library_present(self):
|
||||
json_path = unwrap(self.lib.library_dir) / TS_FOLDER_NAME / JSON_FILENAME
|
||||
return json_path.exists()
|
||||
|
||||
@property
|
||||
def __backups_count(self):
|
||||
backups_path = unwrap(self.lib.library_dir) / TS_FOLDER_NAME / BACKUP_FOLDER_NAME
|
||||
return len(os.listdir(backups_path))
|
||||
|
||||
@property
|
||||
def __backups_size(self):
|
||||
backups_path = unwrap(self.lib.library_dir) / TS_FOLDER_NAME / BACKUP_FOLDER_NAME
|
||||
size: int = 0
|
||||
|
||||
for f in backups_path.glob("*"):
|
||||
if not f.is_dir() and f.exists():
|
||||
size += Path(f).stat().st_size
|
||||
|
||||
return size
|
||||
|
||||
@override
|
||||
def showEvent(self, event: QtGui.QShowEvent): # type: ignore
|
||||
self.refresh()
|
||||
return super().showEvent(event)
|
||||
|
||||
@@ -38,6 +38,7 @@ from tagstudio.core.library.alchemy.enums import SortingModeEnum
|
||||
from tagstudio.qt.controller.widgets.preview_panel_controller import PreviewPanel
|
||||
from tagstudio.qt.flowlayout import FlowLayout
|
||||
from tagstudio.qt.helpers.color_overlay import theme_fg_overlay
|
||||
from tagstudio.qt.mnemonics import assign_mnemonics
|
||||
from tagstudio.qt.pagination import Pagination
|
||||
from tagstudio.qt.platform_strings import trash_term
|
||||
from tagstudio.qt.resource_manager import ResourceManager
|
||||
@@ -80,6 +81,7 @@ class MainMenuBar(QMenuBar):
|
||||
|
||||
tools_menu: QMenu
|
||||
fix_unlinked_entries_action: QAction
|
||||
fix_ignored_entries_action: QAction
|
||||
fix_dupe_files_action: QAction
|
||||
clear_thumb_cache_action: QAction
|
||||
|
||||
@@ -166,6 +168,7 @@ class MainMenuBar(QMenuBar):
|
||||
|
||||
self.file_menu.addSeparator()
|
||||
|
||||
assign_mnemonics(self.file_menu)
|
||||
self.addMenu(self.file_menu)
|
||||
|
||||
def setup_edit_menu(self):
|
||||
@@ -293,6 +296,7 @@ class MainMenuBar(QMenuBar):
|
||||
self.color_manager_action.setEnabled(False)
|
||||
self.edit_menu.addAction(self.color_manager_action)
|
||||
|
||||
assign_mnemonics(self.edit_menu)
|
||||
self.addMenu(self.edit_menu)
|
||||
|
||||
def setup_view_menu(self):
|
||||
@@ -337,6 +341,7 @@ class MainMenuBar(QMenuBar):
|
||||
|
||||
self.view_menu.addSeparator()
|
||||
|
||||
assign_mnemonics(self.view_menu)
|
||||
self.addMenu(self.view_menu)
|
||||
|
||||
def setup_tools_menu(self):
|
||||
@@ -349,6 +354,13 @@ class MainMenuBar(QMenuBar):
|
||||
self.fix_unlinked_entries_action.setEnabled(False)
|
||||
self.tools_menu.addAction(self.fix_unlinked_entries_action)
|
||||
|
||||
# Fix Ignored Entries
|
||||
self.fix_ignored_entries_action = QAction(
|
||||
Translations["menu.tools.fix_ignored_entries"], self
|
||||
)
|
||||
self.fix_ignored_entries_action.setEnabled(False)
|
||||
self.tools_menu.addAction(self.fix_ignored_entries_action)
|
||||
|
||||
# Fix Duplicate Files
|
||||
self.fix_dupe_files_action = QAction(Translations["menu.tools.fix_duplicate_files"], self)
|
||||
self.fix_dupe_files_action.setEnabled(False)
|
||||
@@ -363,6 +375,7 @@ class MainMenuBar(QMenuBar):
|
||||
self.clear_thumb_cache_action.setEnabled(False)
|
||||
self.tools_menu.addAction(self.clear_thumb_cache_action)
|
||||
|
||||
assign_mnemonics(self.tools_menu)
|
||||
self.addMenu(self.tools_menu)
|
||||
|
||||
def setup_macros_menu(self):
|
||||
@@ -372,6 +385,7 @@ class MainMenuBar(QMenuBar):
|
||||
self.folders_to_tags_action.setEnabled(False)
|
||||
self.macros_menu.addAction(self.folders_to_tags_action)
|
||||
|
||||
assign_mnemonics(self.macros_menu)
|
||||
self.addMenu(self.macros_menu)
|
||||
|
||||
def setup_help_menu(self):
|
||||
@@ -380,6 +394,7 @@ class MainMenuBar(QMenuBar):
|
||||
self.about_action = QAction(Translations["menu.help.about"], self)
|
||||
self.help_menu.addAction(self.about_action)
|
||||
|
||||
assign_mnemonics(self.help_menu)
|
||||
self.addMenu(self.help_menu)
|
||||
|
||||
def rebuild_open_recent_library_menu(
|
||||
@@ -510,11 +525,8 @@ class MainWindow(QMainWindow):
|
||||
self.central_layout.setObjectName("central_layout")
|
||||
|
||||
self.setup_search_bar()
|
||||
|
||||
self.setup_extra_input_bar()
|
||||
|
||||
self.setup_content(driver)
|
||||
|
||||
self.setCentralWidget(self.central_widget)
|
||||
|
||||
def setup_search_bar(self):
|
||||
@@ -618,7 +630,6 @@ class MainWindow(QMainWindow):
|
||||
self.content_splitter.setHandleWidth(12)
|
||||
|
||||
self.setup_entry_list(driver)
|
||||
|
||||
self.setup_preview_panel(driver)
|
||||
|
||||
self.content_splitter.setStretchFactor(0, 1)
|
||||
|
||||
142
src/tagstudio/qt/mnemonics.py
Normal file
142
src/tagstudio/qt/mnemonics.py
Normal file
@@ -0,0 +1,142 @@
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
|
||||
from PySide6.QtGui import QAction
|
||||
from PySide6.QtWidgets import QMenu
|
||||
|
||||
|
||||
def remove_mnemonic_marker(label: str) -> str:
|
||||
"""Remove existing accelerator markers (&) from a label."""
|
||||
result = ""
|
||||
skip = False
|
||||
for i, ch in enumerate(label):
|
||||
if skip:
|
||||
skip = False
|
||||
continue
|
||||
if ch == "&":
|
||||
# escaped ampersand "&&"
|
||||
if i + 1 < len(label) and label[i + 1] == "&":
|
||||
result += "&"
|
||||
skip = True
|
||||
# otherwise skip this '&'
|
||||
continue
|
||||
result += ch
|
||||
return result
|
||||
|
||||
|
||||
# Additional weight for first character in string
|
||||
FIRST_CHARACTER_EXTRA_WEIGHT = 50
|
||||
# Additional weight for the beginning of a word
|
||||
WORD_BEGINNING_EXTRA_WEIGHT = 50
|
||||
# Additional weight for a 'wanted' accelerator ie string with '&'
|
||||
WANTED_ACCEL_EXTRA_WEIGHT = 150
|
||||
|
||||
|
||||
def calculate_weights(text: str):
|
||||
weights: dict[int, str] = {}
|
||||
|
||||
pos = 0
|
||||
start_character = True
|
||||
wanted_character = False
|
||||
|
||||
while pos < len(text):
|
||||
c = text[pos]
|
||||
|
||||
# skip non typeable characters
|
||||
if not c.isalnum() and c != "&":
|
||||
start_character = True
|
||||
pos += 1
|
||||
continue
|
||||
|
||||
weight = 1
|
||||
|
||||
# add special weight to first character
|
||||
if pos == 0:
|
||||
weight += FIRST_CHARACTER_EXTRA_WEIGHT
|
||||
elif start_character: # add weight to word beginnings
|
||||
weight += WORD_BEGINNING_EXTRA_WEIGHT
|
||||
start_character = False
|
||||
|
||||
# add weight to characters that have an & beforehand
|
||||
if wanted_character:
|
||||
weight += WANTED_ACCEL_EXTRA_WEIGHT
|
||||
wanted_character = False
|
||||
|
||||
# add decreasing weight to left characters
|
||||
if pos < 50:
|
||||
weight += 50 - pos
|
||||
|
||||
# try to preserve the wanted accelerators
|
||||
if c == "&" and (pos != len(text) - 1 and text[pos + 1] != "&" and text[pos + 1].isalnum()):
|
||||
wanted_character = True
|
||||
pos += 1
|
||||
continue
|
||||
|
||||
while weight in weights:
|
||||
weight += 1
|
||||
|
||||
if c != "&":
|
||||
weights[weight] = c
|
||||
|
||||
pos += 1
|
||||
|
||||
# update our maximum weight
|
||||
max_weight = 0 if len(weights) == 0 else max(weights.keys())
|
||||
return max_weight, weights
|
||||
|
||||
|
||||
def insert_mnemonic(label: str, char: str) -> str:
|
||||
pos = label.lower().find(char)
|
||||
if pos >= 0:
|
||||
return label[:pos] + "&" + label[pos:]
|
||||
return label
|
||||
|
||||
|
||||
def assign_mnemonics(menu: QMenu):
|
||||
# Collect actions
|
||||
actions = [a for a in menu.actions() if not a.isSeparator()]
|
||||
|
||||
# Sequence map: mnemonic key -> QAction
|
||||
sequence_to_action: dict[str, QAction] = {}
|
||||
|
||||
final_text: dict[QAction, str] = {}
|
||||
|
||||
actions.reverse()
|
||||
|
||||
while len(actions) > 0:
|
||||
action = actions.pop()
|
||||
label = action.text()
|
||||
_, weights = calculate_weights(label)
|
||||
|
||||
chosen_char = None
|
||||
|
||||
# Try candidates, starting from highest weight
|
||||
for weight in sorted(weights.keys(), reverse=True):
|
||||
c = weights[weight].lower()
|
||||
other = sequence_to_action.get(c)
|
||||
|
||||
if other is None:
|
||||
chosen_char = c
|
||||
sequence_to_action[c] = action
|
||||
break
|
||||
else:
|
||||
# Compare weights with existing action
|
||||
other_max, _ = calculate_weights(remove_mnemonic_marker(other.text()))
|
||||
if weight > other_max:
|
||||
# Take over from weaker action
|
||||
actions.append(other)
|
||||
sequence_to_action[c] = action
|
||||
chosen_char = c
|
||||
|
||||
# Apply mnemonic if found
|
||||
if chosen_char:
|
||||
plain = remove_mnemonic_marker(label)
|
||||
new_label = insert_mnemonic(plain, chosen_char)
|
||||
final_text[action] = new_label
|
||||
else:
|
||||
# No mnemonic assigned → clean text
|
||||
final_text[action] = remove_mnemonic_marker(label)
|
||||
|
||||
for a, t in final_text.items():
|
||||
a.setText(t)
|
||||
@@ -29,7 +29,7 @@ from tagstudio.core.library.alchemy.enums import TagColorEnum
|
||||
from tagstudio.core.library.alchemy.library import Library, Tag, TagColorGroup
|
||||
from tagstudio.core.palette import ColorType, UiColor, get_tag_color, get_ui_color
|
||||
from tagstudio.qt.modals.tag_color_selection import TagColorSelection
|
||||
from tagstudio.qt.modals.tag_search import TagSearchModal
|
||||
from tagstudio.qt.modals.tag_search import TagSearchModal, TagSearchPanel
|
||||
from tagstudio.qt.translations import Translations
|
||||
from tagstudio.qt.widgets.panel import PanelModal, PanelWidget
|
||||
from tagstudio.qt.widgets.tag import (
|
||||
@@ -384,10 +384,11 @@ class BuildTagPanel(PanelWidget):
|
||||
tag_widget = TagWidget(
|
||||
tag,
|
||||
library=self.lib,
|
||||
has_edit=False,
|
||||
has_edit=True,
|
||||
has_remove=True,
|
||||
)
|
||||
tag_widget.on_remove.connect(lambda t=parent_id: self.remove_parent_tag_callback(t))
|
||||
tag_widget.on_edit.connect(lambda t=tag: TagSearchPanel(library=self.lib).edit_tag(t))
|
||||
row.addWidget(tag_widget)
|
||||
|
||||
# Add Disambiguation Tag Button
|
||||
|
||||
@@ -18,7 +18,7 @@ from PySide6.QtWidgets import (
|
||||
|
||||
from tagstudio.core.library.alchemy.library import Library
|
||||
from tagstudio.core.utils.dupe_files import DupeRegistry
|
||||
from tagstudio.qt.modals.mirror_entities import MirrorEntriesModal
|
||||
from tagstudio.qt.modals.mirror_entries_modal import MirrorEntriesModal
|
||||
from tagstudio.qt.translations import Translations
|
||||
|
||||
# Only import for type checking/autocompletion, will not be imported at runtime.
|
||||
@@ -26,6 +26,7 @@ if TYPE_CHECKING:
|
||||
from tagstudio.qt.ts_qt import QtDriver
|
||||
|
||||
|
||||
# TODO: Break up into MVC classes, similar to fix_ignored_modal
|
||||
class FixDupeFilesModal(QWidget):
|
||||
def __init__(self, library: "Library", driver: "QtDriver"):
|
||||
super().__init__()
|
||||
|
||||
@@ -10,10 +10,10 @@ from PySide6.QtCore import Qt
|
||||
from PySide6.QtWidgets import QHBoxLayout, QLabel, QPushButton, QVBoxLayout, QWidget
|
||||
|
||||
from tagstudio.core.library.alchemy.library import Library
|
||||
from tagstudio.core.utils.missing_files import MissingRegistry
|
||||
from tagstudio.qt.modals.delete_unlinked import DeleteUnlinkedEntriesModal
|
||||
from tagstudio.core.utils.unlinked_registry import UnlinkedRegistry
|
||||
from tagstudio.qt.modals.merge_dupe_entries import MergeDuplicateEntries
|
||||
from tagstudio.qt.modals.relink_unlinked import RelinkUnlinkedEntries
|
||||
from tagstudio.qt.modals.relink_entries_modal import RelinkUnlinkedEntries
|
||||
from tagstudio.qt.modals.remove_unlinked_modal import RemoveUnlinkedEntriesModal
|
||||
from tagstudio.qt.translations import Translations
|
||||
from tagstudio.qt.widgets.progress import ProgressWidget
|
||||
|
||||
@@ -22,15 +22,16 @@ if TYPE_CHECKING:
|
||||
from tagstudio.qt.ts_qt import QtDriver
|
||||
|
||||
|
||||
# TODO: Break up into MVC classes, similar to fix_ignored_modal
|
||||
class FixUnlinkedEntriesModal(QWidget):
|
||||
def __init__(self, library: "Library", driver: "QtDriver"):
|
||||
super().__init__()
|
||||
self.lib = library
|
||||
self.driver = driver
|
||||
|
||||
self.tracker = MissingRegistry(library=self.lib)
|
||||
self.tracker = UnlinkedRegistry(lib=self.lib)
|
||||
|
||||
self.missing_count = -1
|
||||
self.unlinked_count = -1
|
||||
self.dupe_count = -1
|
||||
self.setWindowTitle(Translations["entries.unlinked.title"])
|
||||
self.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||
@@ -43,18 +44,16 @@ class FixUnlinkedEntriesModal(QWidget):
|
||||
self.unlinked_desc_widget.setWordWrap(True)
|
||||
self.unlinked_desc_widget.setStyleSheet("text-align:left;")
|
||||
|
||||
self.missing_count_label = QLabel()
|
||||
self.missing_count_label.setObjectName("missingCountLabel")
|
||||
self.missing_count_label.setStyleSheet("font-weight:bold;font-size:14px;")
|
||||
self.missing_count_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
self.unlinked_count_label = QLabel()
|
||||
self.unlinked_count_label.setObjectName("unlinkedCountLabel")
|
||||
self.unlinked_count_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
self.dupe_count_label = QLabel()
|
||||
self.dupe_count_label.setObjectName("dupeCountLabel")
|
||||
self.dupe_count_label.setStyleSheet("font-weight:bold;font-size:14px;")
|
||||
self.dupe_count_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
self.refresh_unlinked_button = QPushButton(Translations["entries.unlinked.refresh_all"])
|
||||
self.refresh_unlinked_button.clicked.connect(self.refresh_missing_files)
|
||||
self.refresh_unlinked_button = QPushButton(Translations["entries.generic.refresh_alt"])
|
||||
self.refresh_unlinked_button.clicked.connect(self.refresh_unlinked)
|
||||
|
||||
self.merge_class = MergeDuplicateEntries(self.lib, self.driver)
|
||||
self.relink_class = RelinkUnlinkedEntries(self.tracker)
|
||||
@@ -64,7 +63,7 @@ class FixUnlinkedEntriesModal(QWidget):
|
||||
# refresh the grid
|
||||
lambda: (
|
||||
self.driver.update_browsing_state(),
|
||||
self.refresh_missing_files(),
|
||||
self.refresh_unlinked(),
|
||||
)
|
||||
)
|
||||
self.search_button.clicked.connect(self.relink_class.repair_entries)
|
||||
@@ -72,16 +71,17 @@ class FixUnlinkedEntriesModal(QWidget):
|
||||
self.manual_button = QPushButton(Translations["entries.unlinked.relink.manual"])
|
||||
self.manual_button.setHidden(True)
|
||||
|
||||
self.delete_button = QPushButton(Translations["entries.unlinked.delete_alt"])
|
||||
self.delete_modal = DeleteUnlinkedEntriesModal(self.driver, self.tracker)
|
||||
self.delete_modal.done.connect(
|
||||
self.remove_button = QPushButton(Translations["entries.unlinked.remove_alt"])
|
||||
self.remove_modal = RemoveUnlinkedEntriesModal(self.driver, self.tracker)
|
||||
self.remove_modal.done.connect(
|
||||
lambda: (
|
||||
self.set_missing_count(),
|
||||
self.set_unlinked_count(),
|
||||
# refresh the grid
|
||||
self.driver.update_browsing_state(),
|
||||
self.refresh_unlinked(),
|
||||
)
|
||||
)
|
||||
self.delete_button.clicked.connect(self.delete_modal.show)
|
||||
self.remove_button.clicked.connect(self.remove_modal.show)
|
||||
|
||||
self.button_container = QWidget()
|
||||
self.button_layout = QHBoxLayout(self.button_container)
|
||||
@@ -93,19 +93,19 @@ class FixUnlinkedEntriesModal(QWidget):
|
||||
self.done_button.clicked.connect(self.hide)
|
||||
self.button_layout.addWidget(self.done_button)
|
||||
|
||||
self.root_layout.addWidget(self.missing_count_label)
|
||||
self.root_layout.addWidget(self.unlinked_count_label)
|
||||
self.root_layout.addWidget(self.unlinked_desc_widget)
|
||||
self.root_layout.addWidget(self.refresh_unlinked_button)
|
||||
self.root_layout.addWidget(self.search_button)
|
||||
self.root_layout.addWidget(self.manual_button)
|
||||
self.root_layout.addWidget(self.delete_button)
|
||||
self.root_layout.addWidget(self.remove_button)
|
||||
self.root_layout.addStretch(1)
|
||||
self.root_layout.addStretch(2)
|
||||
self.root_layout.addWidget(self.button_container)
|
||||
|
||||
self.set_missing_count(self.missing_count)
|
||||
self.update_unlinked_count()
|
||||
|
||||
def refresh_missing_files(self):
|
||||
def refresh_unlinked(self):
|
||||
pw = ProgressWidget(
|
||||
cancel_button_text=None,
|
||||
minimum=0,
|
||||
@@ -114,30 +114,47 @@ class FixUnlinkedEntriesModal(QWidget):
|
||||
pw.setWindowTitle(Translations["library.scan_library.title"])
|
||||
pw.update_label(Translations["entries.unlinked.scanning"])
|
||||
|
||||
def update_driver_widgets():
|
||||
if (
|
||||
hasattr(self.driver, "library_info_window")
|
||||
and self.driver.library_info_window.isVisible()
|
||||
):
|
||||
self.driver.library_info_window.update_cleanup()
|
||||
|
||||
pw.from_iterable_function(
|
||||
self.tracker.refresh_missing_files,
|
||||
self.tracker.refresh_unlinked_files,
|
||||
None,
|
||||
self.set_missing_count,
|
||||
self.delete_modal.refresh_list,
|
||||
self.set_unlinked_count,
|
||||
self.update_unlinked_count,
|
||||
self.remove_modal.refresh_list,
|
||||
update_driver_widgets,
|
||||
)
|
||||
|
||||
def set_missing_count(self, count: int | None = None):
|
||||
if count is not None:
|
||||
self.missing_count = count
|
||||
else:
|
||||
self.missing_count = self.tracker.missing_file_entries_count
|
||||
def set_unlinked_count(self):
|
||||
"""Sets the unlinked_entries_count in the Library to the tracker's value."""
|
||||
self.lib.unlinked_entries_count = self.tracker.unlinked_entries_count
|
||||
|
||||
if self.missing_count < 0:
|
||||
self.search_button.setDisabled(True)
|
||||
self.delete_button.setDisabled(True)
|
||||
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(
|
||||
Translations.format("entries.unlinked.missing_count.some", count=self.missing_count)
|
||||
)
|
||||
def update_unlinked_count(self):
|
||||
"""Updates the UI to reflect the Library's current unlinked_entries_count."""
|
||||
# Indicates that the library is new compared to the last update.
|
||||
# NOTE: Make sure set_unlinked_count() is called before this!
|
||||
if self.tracker.unlinked_entries_count > 0 and self.lib.unlinked_entries_count < 0:
|
||||
self.tracker.reset()
|
||||
|
||||
count: int = self.lib.unlinked_entries_count
|
||||
|
||||
self.search_button.setDisabled(count < 1)
|
||||
self.remove_button.setDisabled(count < 1)
|
||||
|
||||
count_text: str = Translations.format(
|
||||
"entries.unlinked.unlinked_count", count=count if count >= 0 else "—"
|
||||
)
|
||||
self.unlinked_count_label.setText(f"<h3>{count_text}</h3>")
|
||||
|
||||
@override
|
||||
def showEvent(self, event: QtGui.QShowEvent) -> None:
|
||||
self.update_unlinked_count()
|
||||
return super().showEvent(event)
|
||||
|
||||
@override
|
||||
def keyPressEvent(self, event: QtGui.QKeyEvent) -> None: # noqa N802
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
from PySide6.QtCore import QObject, Signal
|
||||
|
||||
from tagstudio.core.utils.missing_files import MissingRegistry
|
||||
from tagstudio.core.utils.unlinked_registry import UnlinkedRegistry
|
||||
from tagstudio.qt.translations import Translations
|
||||
from tagstudio.qt.widgets.progress import ProgressWidget
|
||||
|
||||
@@ -13,7 +13,7 @@ from tagstudio.qt.widgets.progress import ProgressWidget
|
||||
class RelinkUnlinkedEntries(QObject):
|
||||
done = Signal()
|
||||
|
||||
def __init__(self, tracker: MissingRegistry):
|
||||
def __init__(self, tracker: UnlinkedRegistry):
|
||||
super().__init__()
|
||||
self.tracker = tracker
|
||||
|
||||
@@ -21,8 +21,8 @@ class RelinkUnlinkedEntries(QObject):
|
||||
def displayed_text(x):
|
||||
return Translations.format(
|
||||
"entries.unlinked.relink.attempting",
|
||||
idx=x,
|
||||
missing_count=self.tracker.missing_file_entries_count,
|
||||
index=x,
|
||||
unlinked_count=self.tracker.unlinked_entries_count,
|
||||
fixed_count=self.tracker.files_fixed_count,
|
||||
)
|
||||
|
||||
@@ -30,8 +30,7 @@ class RelinkUnlinkedEntries(QObject):
|
||||
label_text="",
|
||||
cancel_button_text=None,
|
||||
minimum=0,
|
||||
maximum=self.tracker.missing_file_entries_count,
|
||||
maximum=self.tracker.unlinked_entries_count,
|
||||
)
|
||||
pw.setWindowTitle(Translations["entries.unlinked.relink.title"])
|
||||
|
||||
pw.from_iterable_function(self.tracker.fix_unlinked_entries, displayed_text, self.done.emit)
|
||||
@@ -17,7 +17,7 @@ from PySide6.QtWidgets import (
|
||||
QWidget,
|
||||
)
|
||||
|
||||
from tagstudio.core.utils.missing_files import MissingRegistry
|
||||
from tagstudio.core.utils.ignored_registry import IgnoredRegistry
|
||||
from tagstudio.qt.helpers.custom_runnable import CustomRunnable
|
||||
from tagstudio.qt.translations import Translations
|
||||
from tagstudio.qt.widgets.progress import ProgressWidget
|
||||
@@ -27,14 +27,14 @@ if TYPE_CHECKING:
|
||||
from tagstudio.qt.ts_qt import QtDriver
|
||||
|
||||
|
||||
class DeleteUnlinkedEntriesModal(QWidget):
|
||||
class RemoveIgnoredModal(QWidget):
|
||||
done = Signal()
|
||||
|
||||
def __init__(self, driver: "QtDriver", tracker: MissingRegistry):
|
||||
def __init__(self, driver: "QtDriver", tracker: IgnoredRegistry):
|
||||
super().__init__()
|
||||
self.driver = driver
|
||||
self.tracker = tracker
|
||||
self.setWindowTitle(Translations["entries.unlinked.delete"])
|
||||
self.setWindowTitle(Translations["entries.ignored.remove"])
|
||||
self.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||
self.setMinimumSize(500, 400)
|
||||
self.root_layout = QVBoxLayout(self)
|
||||
@@ -42,8 +42,8 @@ class DeleteUnlinkedEntriesModal(QWidget):
|
||||
|
||||
self.desc_widget = QLabel(
|
||||
Translations.format(
|
||||
"entries.unlinked.delete.confirm",
|
||||
count=self.tracker.missing_file_entries_count,
|
||||
"entries.remove.plural.confirm",
|
||||
count=self.tracker.ignored_count,
|
||||
)
|
||||
)
|
||||
self.desc_widget.setObjectName("descriptionLabel")
|
||||
@@ -64,7 +64,7 @@ class DeleteUnlinkedEntriesModal(QWidget):
|
||||
self.cancel_button.clicked.connect(self.hide)
|
||||
self.button_layout.addWidget(self.cancel_button)
|
||||
|
||||
self.delete_button = QPushButton(Translations["generic.delete_alt"])
|
||||
self.delete_button = QPushButton(Translations["generic.remove_alt"])
|
||||
self.delete_button.clicked.connect(self.hide)
|
||||
self.delete_button.clicked.connect(lambda: self.delete_entries())
|
||||
self.button_layout.addWidget(self.delete_button)
|
||||
@@ -75,13 +75,11 @@ class DeleteUnlinkedEntriesModal(QWidget):
|
||||
|
||||
def refresh_list(self):
|
||||
self.desc_widget.setText(
|
||||
Translations.format(
|
||||
"entries.unlinked.delete.confirm", count=self.tracker.missing_file_entries_count
|
||||
)
|
||||
Translations.format("entries.remove.plural.confirm", count=self.tracker.ignored_count)
|
||||
)
|
||||
|
||||
self.model.clear()
|
||||
for i in self.tracker.missing_file_entries:
|
||||
for i in self.tracker.ignored_entries:
|
||||
item = QStandardItem(str(i.path))
|
||||
item.setEditable(False)
|
||||
self.model.appendRow(item)
|
||||
@@ -92,11 +90,15 @@ class DeleteUnlinkedEntriesModal(QWidget):
|
||||
minimum=0,
|
||||
maximum=0,
|
||||
)
|
||||
pw.setWindowTitle(Translations["entries.unlinked.delete.deleting"])
|
||||
pw.update_label(Translations["entries.unlinked.delete.deleting"])
|
||||
pw.setWindowTitle(Translations["entries.generic.remove.removing"])
|
||||
pw.update_label(
|
||||
Translations.format(
|
||||
"entries.generic.remove.removing_count", count=self.tracker.ignored_count
|
||||
)
|
||||
)
|
||||
pw.show()
|
||||
|
||||
r = CustomRunnable(self.tracker.execute_deletion)
|
||||
r = CustomRunnable(self.tracker.remove_ignored_entries)
|
||||
QThreadPool.globalInstance().start(r)
|
||||
r.done.connect(
|
||||
lambda: (
|
||||
119
src/tagstudio/qt/modals/remove_unlinked_modal.py
Normal file
119
src/tagstudio/qt/modals/remove_unlinked_modal.py
Normal file
@@ -0,0 +1,119 @@
|
||||
# Copyright (C) 2025 Travis Abendshien (CyanVoxel).
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
|
||||
from typing import TYPE_CHECKING, override
|
||||
|
||||
from PySide6 import QtCore, QtGui
|
||||
from PySide6.QtCore import Qt, QThreadPool, Signal
|
||||
from PySide6.QtGui import QStandardItem, QStandardItemModel
|
||||
from PySide6.QtWidgets import (
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QListView,
|
||||
QPushButton,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
|
||||
from tagstudio.core.utils.unlinked_registry import UnlinkedRegistry
|
||||
from tagstudio.qt.helpers.custom_runnable import CustomRunnable
|
||||
from tagstudio.qt.translations import Translations
|
||||
from tagstudio.qt.widgets.progress import ProgressWidget
|
||||
|
||||
# Only import for type checking/autocompletion, will not be imported at runtime.
|
||||
if TYPE_CHECKING:
|
||||
from tagstudio.qt.ts_qt import QtDriver
|
||||
|
||||
|
||||
class RemoveUnlinkedEntriesModal(QWidget):
|
||||
done = Signal()
|
||||
|
||||
def __init__(self, driver: "QtDriver", tracker: UnlinkedRegistry):
|
||||
super().__init__()
|
||||
self.driver = driver
|
||||
self.tracker = tracker
|
||||
self.setWindowTitle(Translations["entries.unlinked.remove"])
|
||||
self.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||
self.setMinimumSize(500, 400)
|
||||
self.root_layout = QVBoxLayout(self)
|
||||
self.root_layout.setContentsMargins(6, 6, 6, 6)
|
||||
|
||||
self.desc_widget = QLabel(
|
||||
Translations.format(
|
||||
"entries.remove.plural.confirm",
|
||||
count=self.tracker.unlinked_entries_count,
|
||||
)
|
||||
)
|
||||
self.desc_widget.setObjectName("descriptionLabel")
|
||||
self.desc_widget.setWordWrap(True)
|
||||
self.desc_widget.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
self.list_view = QListView()
|
||||
self.model = QStandardItemModel()
|
||||
self.list_view.setModel(self.model)
|
||||
|
||||
self.button_container = QWidget()
|
||||
self.button_layout = QHBoxLayout(self.button_container)
|
||||
self.button_layout.setContentsMargins(6, 6, 6, 6)
|
||||
self.button_layout.addStretch(1)
|
||||
|
||||
self.cancel_button = QPushButton(Translations["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(Translations["generic.remove_alt"])
|
||||
self.delete_button.clicked.connect(self.hide)
|
||||
self.delete_button.clicked.connect(lambda: self.remove_entries())
|
||||
self.button_layout.addWidget(self.delete_button)
|
||||
|
||||
self.root_layout.addWidget(self.desc_widget)
|
||||
self.root_layout.addWidget(self.list_view)
|
||||
self.root_layout.addWidget(self.button_container)
|
||||
|
||||
def refresh_list(self):
|
||||
self.desc_widget.setText(
|
||||
Translations.format(
|
||||
"entries.remove.plural.confirm", count=self.tracker.unlinked_entries_count
|
||||
)
|
||||
)
|
||||
|
||||
self.model.clear()
|
||||
for i in self.tracker.unlinked_entries:
|
||||
item = QStandardItem(str(i.path))
|
||||
item.setEditable(False)
|
||||
self.model.appendRow(item)
|
||||
|
||||
def remove_entries(self):
|
||||
pw = ProgressWidget(
|
||||
cancel_button_text=None,
|
||||
minimum=0,
|
||||
maximum=0,
|
||||
)
|
||||
pw.setWindowTitle(Translations["entries.generic.remove.removing"])
|
||||
pw.update_label(
|
||||
Translations.format(
|
||||
"entries.generic.remove.removing_count", count=self.tracker.unlinked_entries_count
|
||||
)
|
||||
)
|
||||
pw.show()
|
||||
|
||||
r = CustomRunnable(self.tracker.remove_unlinked_entries)
|
||||
QThreadPool.globalInstance().start(r)
|
||||
r.done.connect(
|
||||
lambda: (
|
||||
pw.hide(),
|
||||
pw.deleteLater(),
|
||||
self.done.emit(),
|
||||
)
|
||||
)
|
||||
|
||||
@override
|
||||
def keyPressEvent(self, event: QtGui.QKeyEvent) -> None: # noqa N802
|
||||
if event.key() == QtCore.Qt.Key.Key_Escape:
|
||||
self.cancel_button.click()
|
||||
else: # Other key presses
|
||||
pass
|
||||
return super().keyPressEvent(event)
|
||||
@@ -3,7 +3,7 @@
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtWidgets import (
|
||||
@@ -18,65 +18,65 @@ from PySide6.QtWidgets import (
|
||||
)
|
||||
|
||||
from tagstudio.core.enums import ShowFilepathOption, TagClickActionOption
|
||||
from tagstudio.core.global_settings import Theme
|
||||
from tagstudio.core.global_settings import Splash, Theme
|
||||
from tagstudio.qt.translations import DEFAULT_TRANSLATION, LANGUAGES, Translations
|
||||
from tagstudio.qt.widgets.panel import PanelModal, PanelWidget
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from tagstudio.qt.ts_qt import QtDriver
|
||||
|
||||
FILEPATH_OPTION_MAP: dict[ShowFilepathOption, str] = {}
|
||||
|
||||
THEME_MAP: dict[Theme, str] = {}
|
||||
|
||||
TAG_CLICK_ACTION_MAP: dict[TagClickActionOption, str] = {}
|
||||
|
||||
DATE_FORMAT_MAP: dict[str, str] = {
|
||||
"%d/%m/%y": "21/08/24",
|
||||
"%d/%m/%Y": "21/08/2024",
|
||||
"%d.%m.%y": "21.08.24",
|
||||
"%d.%m.%Y": "21.08.2024",
|
||||
"%d-%m-%y": "21-08-24",
|
||||
"%d-%m-%Y": "21-08-2024",
|
||||
"%x": "08/21/24",
|
||||
"%m/%d/%Y": "08/21/2024",
|
||||
"%m-%d-%y": "08-21-24",
|
||||
"%m-%d-%Y": "08-21-2024",
|
||||
"%m.%d.%y": "08.21.24",
|
||||
"%m.%d.%Y": "08.21.2024",
|
||||
"%Y/%m/%d": "2024/08/21",
|
||||
"%Y-%m-%d": "2024-08-21",
|
||||
"%Y.%m.%d": "2024.08.21",
|
||||
}
|
||||
|
||||
|
||||
class SettingsPanel(PanelWidget):
|
||||
driver: "QtDriver"
|
||||
|
||||
filepath_option_map: dict[ShowFilepathOption, str] = {
|
||||
ShowFilepathOption.SHOW_FULL_PATHS: Translations["settings.filepath.option.full"],
|
||||
ShowFilepathOption.SHOW_RELATIVE_PATHS: Translations["settings.filepath.option.relative"],
|
||||
ShowFilepathOption.SHOW_FILENAMES_ONLY: Translations["settings.filepath.option.name"],
|
||||
}
|
||||
|
||||
theme_map: dict[Theme, str] = {
|
||||
Theme.SYSTEM: Translations["settings.theme.system"],
|
||||
Theme.DARK: Translations["settings.theme.dark"],
|
||||
Theme.LIGHT: Translations["settings.theme.light"],
|
||||
}
|
||||
|
||||
splash_map: dict[Splash, str] = {
|
||||
Splash.DEFAULT: Translations["settings.splash.option.default"],
|
||||
Splash.RANDOM: Translations["settings.splash.option.random"],
|
||||
Splash.CLASSIC: Translations["settings.splash.option.classic"],
|
||||
Splash.GOO_GEARS: Translations["settings.splash.option.goo_gears"],
|
||||
Splash.NINETY_FIVE: Translations["settings.splash.option.ninety_five"],
|
||||
}
|
||||
|
||||
tag_click_action_map: dict[TagClickActionOption, str] = {
|
||||
TagClickActionOption.OPEN_EDIT: Translations["settings.tag_click_action.open_edit"],
|
||||
TagClickActionOption.SET_SEARCH: Translations["settings.tag_click_action.set_search"],
|
||||
TagClickActionOption.ADD_TO_SEARCH: Translations["settings.tag_click_action.add_to_search"],
|
||||
}
|
||||
|
||||
date_format_map: dict[str, str] = {
|
||||
"%d/%m/%y": "21/08/24",
|
||||
"%d/%m/%Y": "21/08/2024",
|
||||
"%d.%m.%y": "21.08.24",
|
||||
"%d.%m.%Y": "21.08.2024",
|
||||
"%d-%m-%y": "21-08-24",
|
||||
"%d-%m-%Y": "21-08-2024",
|
||||
"%x": "08/21/24",
|
||||
"%m/%d/%Y": "08/21/2024",
|
||||
"%m-%d-%y": "08-21-24",
|
||||
"%m-%d-%Y": "08-21-2024",
|
||||
"%m.%d.%y": "08.21.24",
|
||||
"%m.%d.%Y": "08.21.2024",
|
||||
"%Y/%m/%d": "2024/08/21",
|
||||
"%Y-%m-%d": "2024-08-21",
|
||||
"%Y.%m.%d": "2024.08.21",
|
||||
}
|
||||
|
||||
def __init__(self, driver: "QtDriver"):
|
||||
super().__init__()
|
||||
# set these "constants" because language will be loaded from config shortly after startup
|
||||
# and we want to use the current language for the dropdowns
|
||||
global FILEPATH_OPTION_MAP, THEME_MAP, TAG_CLICK_ACTION_MAP
|
||||
FILEPATH_OPTION_MAP = {
|
||||
ShowFilepathOption.SHOW_FULL_PATHS: Translations["settings.filepath.option.full"],
|
||||
ShowFilepathOption.SHOW_RELATIVE_PATHS: Translations[
|
||||
"settings.filepath.option.relative"
|
||||
],
|
||||
ShowFilepathOption.SHOW_FILENAMES_ONLY: Translations["settings.filepath.option.name"],
|
||||
}
|
||||
THEME_MAP = {
|
||||
Theme.DARK: Translations["settings.theme.dark"],
|
||||
Theme.LIGHT: Translations["settings.theme.light"],
|
||||
Theme.SYSTEM: Translations["settings.theme.system"],
|
||||
}
|
||||
TAG_CLICK_ACTION_MAP = {
|
||||
TagClickActionOption.OPEN_EDIT: Translations["settings.tag_click_action.open_edit"],
|
||||
TagClickActionOption.SET_SEARCH: Translations["settings.tag_click_action.set_search"],
|
||||
TagClickActionOption.ADD_TO_SEARCH: Translations[
|
||||
"settings.tag_click_action.add_to_search"
|
||||
],
|
||||
}
|
||||
|
||||
self.driver = driver
|
||||
self.setMinimumSize(400, 300)
|
||||
@@ -84,6 +84,8 @@ class SettingsPanel(PanelWidget):
|
||||
self.root_layout = QVBoxLayout(self)
|
||||
self.root_layout.setContentsMargins(0, 6, 0, 0)
|
||||
|
||||
self.library_settings_container = QWidget()
|
||||
|
||||
# Tabs
|
||||
self.tab_widget = QTabWidget()
|
||||
|
||||
@@ -135,6 +137,7 @@ class SettingsPanel(PanelWidget):
|
||||
Translations["settings.open_library_on_start"], self.open_last_lib_checkbox
|
||||
)
|
||||
|
||||
# Generate Thumbnails
|
||||
self.generate_thumbs = QCheckBox()
|
||||
self.generate_thumbs.setChecked(self.driver.settings.generate_thumbs)
|
||||
form_layout.addRow(Translations["settings.generate_thumbs"], self.generate_thumbs)
|
||||
@@ -165,49 +168,61 @@ class SettingsPanel(PanelWidget):
|
||||
|
||||
# Show Filepath
|
||||
self.filepath_combobox = QComboBox()
|
||||
for k in FILEPATH_OPTION_MAP:
|
||||
self.filepath_combobox.addItem(FILEPATH_OPTION_MAP[k], k)
|
||||
for k in SettingsPanel.filepath_option_map:
|
||||
self.filepath_combobox.addItem(SettingsPanel.filepath_option_map[k], k)
|
||||
filepath_option: ShowFilepathOption = self.driver.settings.show_filepath
|
||||
if filepath_option not in FILEPATH_OPTION_MAP:
|
||||
if filepath_option not in SettingsPanel.filepath_option_map:
|
||||
filepath_option = ShowFilepathOption.DEFAULT
|
||||
self.filepath_combobox.setCurrentIndex(
|
||||
list(FILEPATH_OPTION_MAP.keys()).index(filepath_option)
|
||||
list(SettingsPanel.filepath_option_map.keys()).index(filepath_option)
|
||||
)
|
||||
form_layout.addRow(Translations["settings.filepath.label"], self.filepath_combobox)
|
||||
|
||||
# Dark Mode
|
||||
self.theme_combobox = QComboBox()
|
||||
for k in THEME_MAP:
|
||||
self.theme_combobox.addItem(THEME_MAP[k], k)
|
||||
theme = self.driver.settings.theme
|
||||
if theme not in THEME_MAP:
|
||||
theme = Theme.DEFAULT
|
||||
self.theme_combobox.setCurrentIndex(list(THEME_MAP.keys()).index(theme))
|
||||
self.theme_combobox.currentIndexChanged.connect(self.__update_restart_label)
|
||||
form_layout.addRow(Translations["settings.theme.label"], self.theme_combobox)
|
||||
|
||||
# Tag Click Action
|
||||
self.tag_click_action_combobox = QComboBox()
|
||||
for k in TAG_CLICK_ACTION_MAP:
|
||||
self.tag_click_action_combobox.addItem(TAG_CLICK_ACTION_MAP[k], k)
|
||||
for k in SettingsPanel.tag_click_action_map:
|
||||
self.tag_click_action_combobox.addItem(SettingsPanel.tag_click_action_map[k], k)
|
||||
tag_click_action = self.driver.settings.tag_click_action
|
||||
if tag_click_action not in TAG_CLICK_ACTION_MAP:
|
||||
if tag_click_action not in SettingsPanel.tag_click_action_map:
|
||||
tag_click_action = TagClickActionOption.DEFAULT
|
||||
self.tag_click_action_combobox.setCurrentIndex(
|
||||
list(TAG_CLICK_ACTION_MAP.keys()).index(tag_click_action)
|
||||
list(SettingsPanel.tag_click_action_map.keys()).index(tag_click_action)
|
||||
)
|
||||
form_layout.addRow(
|
||||
Translations["settings.tag_click_action.label"], self.tag_click_action_combobox
|
||||
)
|
||||
|
||||
# Dark Mode
|
||||
self.theme_combobox = QComboBox()
|
||||
for k in SettingsPanel.theme_map:
|
||||
self.theme_combobox.addItem(SettingsPanel.theme_map[k], k)
|
||||
theme = self.driver.settings.theme
|
||||
if theme not in SettingsPanel.theme_map:
|
||||
theme = Theme.DEFAULT
|
||||
self.theme_combobox.setCurrentIndex(list(SettingsPanel.theme_map.keys()).index(theme))
|
||||
self.theme_combobox.currentIndexChanged.connect(self.__update_restart_label)
|
||||
form_layout.addRow(Translations["settings.theme.label"], self.theme_combobox)
|
||||
|
||||
# Splash Screen
|
||||
self.splash_combobox = QComboBox()
|
||||
for k in SettingsPanel.splash_map:
|
||||
self.splash_combobox.addItem(SettingsPanel.splash_map[k], k)
|
||||
splash = self.driver.settings.splash
|
||||
if splash not in SettingsPanel.splash_map:
|
||||
splash = Splash.DEFAULT
|
||||
self.splash_combobox.setCurrentIndex(list(SettingsPanel.splash_map.keys()).index(splash))
|
||||
form_layout.addRow(Translations["settings.splash.label"], self.splash_combobox)
|
||||
|
||||
# Date Format
|
||||
self.dateformat_combobox = QComboBox()
|
||||
for k in DATE_FORMAT_MAP:
|
||||
self.dateformat_combobox.addItem(DATE_FORMAT_MAP[k], k)
|
||||
for k in SettingsPanel.date_format_map:
|
||||
self.dateformat_combobox.addItem(SettingsPanel.date_format_map[k], k)
|
||||
dateformat: str = self.driver.settings.date_format
|
||||
if dateformat not in DATE_FORMAT_MAP:
|
||||
if dateformat not in SettingsPanel.date_format_map:
|
||||
dateformat = "%x"
|
||||
self.dateformat_combobox.setCurrentIndex(list(DATE_FORMAT_MAP.keys()).index(dateformat))
|
||||
self.dateformat_combobox.setCurrentIndex(
|
||||
list(SettingsPanel.date_format_map.keys()).index(dateformat)
|
||||
)
|
||||
self.dateformat_combobox.currentIndexChanged.connect(self.__update_restart_label)
|
||||
form_layout.addRow(Translations["settings.dateformat.label"], self.dateformat_combobox)
|
||||
|
||||
@@ -221,8 +236,8 @@ class SettingsPanel(PanelWidget):
|
||||
self.zeropadding_checkbox.setChecked(self.driver.settings.zero_padding)
|
||||
form_layout.addRow(Translations["settings.zeropadding.label"], self.zeropadding_checkbox)
|
||||
|
||||
def __build_library_settings(self):
|
||||
self.library_settings_container = QWidget()
|
||||
# TODO: Implement Library Settings
|
||||
def __build_library_settings(self): # pyright: ignore[reportUnusedFunction]
|
||||
form_layout = QFormLayout(self.library_settings_container)
|
||||
form_layout.setContentsMargins(6, 6, 6, 6)
|
||||
|
||||
@@ -232,7 +247,7 @@ class SettingsPanel(PanelWidget):
|
||||
def __get_language(self) -> str:
|
||||
return list(LANGUAGES.values())[self.language_combobox.currentIndex()]
|
||||
|
||||
def get_settings(self) -> dict:
|
||||
def get_settings(self) -> dict[str, Any]: # pyright: ignore[reportExplicitAny]
|
||||
return {
|
||||
"language": self.__get_language(),
|
||||
"open_last_loaded_on_startup": self.open_last_lib_checkbox.isChecked(),
|
||||
@@ -246,6 +261,7 @@ class SettingsPanel(PanelWidget):
|
||||
"date_format": self.dateformat_combobox.currentData(),
|
||||
"hour_format": self.hourformat_checkbox.isChecked(),
|
||||
"zero_padding": self.zeropadding_checkbox.isChecked(),
|
||||
"splash": self.splash_combobox.currentData(),
|
||||
}
|
||||
|
||||
def update_settings(self, driver: "QtDriver"):
|
||||
@@ -263,6 +279,7 @@ class SettingsPanel(PanelWidget):
|
||||
driver.settings.date_format = settings["date_format"]
|
||||
driver.settings.hour_format = settings["hour_format"]
|
||||
driver.settings.zero_padding = settings["zero_padding"]
|
||||
driver.settings.splash = settings["splash"]
|
||||
|
||||
driver.settings.save()
|
||||
|
||||
|
||||
@@ -1,138 +1,150 @@
|
||||
{
|
||||
"splash_classic": {
|
||||
"path": "qt/images/splash/classic.png",
|
||||
"mode": "qpixmap"
|
||||
},
|
||||
"splash_goo_gears": {
|
||||
"path": "qt/images/splash/goo_gears.png",
|
||||
"mode": "qpixmap"
|
||||
},
|
||||
"icon": {
|
||||
"path": "icon.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"play_icon": {
|
||||
"path": "qt/images/play.svg",
|
||||
"mode": "rb"
|
||||
},
|
||||
"pause_icon": {
|
||||
"path": "qt/images/pause.svg",
|
||||
"mode": "rb"
|
||||
},
|
||||
"volume_icon": {
|
||||
"path": "qt/images/volume.svg",
|
||||
"mode": "rb"
|
||||
},
|
||||
"volume_mute_icon": {
|
||||
"path": "qt/images/volume_mute.svg",
|
||||
"mode": "rb"
|
||||
},
|
||||
"broken_link_icon": {
|
||||
"path": "qt/images/broken_link_icon.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"ignored": {
|
||||
"path": "qt/images/ignored_128.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"adobe_illustrator": {
|
||||
"path": "qt/images/file_icons/adobe_illustrator.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"adobe_photoshop": {
|
||||
"path": "qt/images/file_icons/adobe_photoshop.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"affinity_photo": {
|
||||
"path": "qt/images/file_icons/affinity_photo.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"archive": {
|
||||
"path": "qt/images/file_icons/archive.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"audio": {
|
||||
"path": "qt/images/file_icons/audio.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"blender": {
|
||||
"path": "qt/images/file_icons/blender.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"database": {
|
||||
"path": "qt/images/file_icons/database.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"document": {
|
||||
"path": "qt/images/file_icons/document.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"ebook": {
|
||||
"path": "qt/images/file_icons/ebook.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"file_generic": {
|
||||
"path": "qt/images/file_icons/file_generic.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"font": {
|
||||
"path": "qt/images/file_icons/font.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"image": {
|
||||
"path": "qt/images/file_icons/image.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"image_vector": {
|
||||
"path": "qt/images/file_icons/image_vector.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"material": {
|
||||
"path": "qt/images/file_icons/material.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"model": {
|
||||
"path": "qt/images/file_icons/model.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"presentation": {
|
||||
"path": "qt/images/file_icons/presentation.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"program": {
|
||||
"path": "qt/images/file_icons/program.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"shader": {
|
||||
"path": "qt/images/file_icons/shader.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"shortcut": {
|
||||
"path": "qt/images/file_icons/shortcut.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"spreadsheet": {
|
||||
"path": "qt/images/file_icons/spreadsheet.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"text": {
|
||||
"path": "qt/images/file_icons/text.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"video": {
|
||||
"path": "qt/images/file_icons/video.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"thumb_loading": {
|
||||
"path": "qt/images/thumb_loading.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"bxs-left-arrow": {
|
||||
"path": "qt/images/bxs-left-arrow.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"bxs-right-arrow": {
|
||||
"path": "qt/images/bxs-right-arrow.png",
|
||||
"mode": "pil"
|
||||
}
|
||||
"splash_classic": {
|
||||
"path": "qt/images/splash/classic.png",
|
||||
"mode": "qpixmap"
|
||||
},
|
||||
"splash_goo_gears": {
|
||||
"path": "qt/images/splash/goo_gears.png",
|
||||
"mode": "qpixmap"
|
||||
},
|
||||
"splash_95": {
|
||||
"path": "qt/images/splash/95.png",
|
||||
"mode": "qpixmap"
|
||||
},
|
||||
"icon": {
|
||||
"path": "icon.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"play_icon": {
|
||||
"path": "qt/images/play.svg",
|
||||
"mode": "rb"
|
||||
},
|
||||
"pause_icon": {
|
||||
"path": "qt/images/pause.svg",
|
||||
"mode": "rb"
|
||||
},
|
||||
"volume_icon": {
|
||||
"path": "qt/images/volume.svg",
|
||||
"mode": "rb"
|
||||
},
|
||||
"volume_mute_icon": {
|
||||
"path": "qt/images/volume_mute.svg",
|
||||
"mode": "rb"
|
||||
},
|
||||
"broken_link_icon": {
|
||||
"path": "qt/images/broken_link_icon.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"ignored": {
|
||||
"path": "qt/images/ignored_128.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"adobe_illustrator": {
|
||||
"path": "qt/images/file_icons/adobe_illustrator.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"adobe_photoshop": {
|
||||
"path": "qt/images/file_icons/adobe_photoshop.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"affinity_photo": {
|
||||
"path": "qt/images/file_icons/affinity_photo.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"archive": {
|
||||
"path": "qt/images/file_icons/archive.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"audio": {
|
||||
"path": "qt/images/file_icons/audio.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"database": {
|
||||
"path": "qt/images/file_icons/database.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"document": {
|
||||
"path": "qt/images/file_icons/document.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"ebook": {
|
||||
"path": "qt/images/file_icons/ebook.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"file_generic": {
|
||||
"path": "qt/images/file_icons/file_generic.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"font": {
|
||||
"path": "qt/images/file_icons/font.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"image": {
|
||||
"path": "qt/images/file_icons/image.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"image_vector": {
|
||||
"path": "qt/images/file_icons/image_vector.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"material": {
|
||||
"path": "qt/images/file_icons/material.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"model": {
|
||||
"path": "qt/images/file_icons/model.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"presentation": {
|
||||
"path": "qt/images/file_icons/presentation.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"program": {
|
||||
"path": "qt/images/file_icons/program.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"shader": {
|
||||
"path": "qt/images/file_icons/shader.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"shortcut": {
|
||||
"path": "qt/images/file_icons/shortcut.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"spreadsheet": {
|
||||
"path": "qt/images/file_icons/spreadsheet.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"text": {
|
||||
"path": "qt/images/file_icons/text.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"video": {
|
||||
"path": "qt/images/file_icons/video.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"thumb_loading": {
|
||||
"path": "qt/images/thumb_loading.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"bxs-left-arrow": {
|
||||
"path": "qt/images/bxs-left-arrow.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"bxs-right-arrow": {
|
||||
"path": "qt/images/bxs-right-arrow.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"unlinked_stat": {
|
||||
"path": "qt/images/unlinked_stat.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"ignored_stat": {
|
||||
"path": "qt/images/ignored_stat.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"dupe_file_stat": {
|
||||
"path": "qt/images/dupe_file_stat.png",
|
||||
"mode": "pil"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
|
||||
import math
|
||||
import random
|
||||
|
||||
import structlog
|
||||
from PySide6.QtCore import QRect, Qt
|
||||
@@ -11,12 +12,13 @@ from PySide6.QtGui import QColor, QFont, QPainter, QPen, QPixmap
|
||||
from PySide6.QtWidgets import QSplashScreen, QWidget
|
||||
|
||||
from tagstudio.core.constants import VERSION, VERSION_BRANCH
|
||||
from tagstudio.core.global_settings import Splash
|
||||
from tagstudio.qt.resource_manager import ResourceManager
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
class Splash:
|
||||
class SplashScreen:
|
||||
"""The custom splash screen widget for TagStudio."""
|
||||
|
||||
COPYRIGHT_YEARS: str = "2021-2025"
|
||||
@@ -24,11 +26,7 @@ class Splash:
|
||||
VERSION_STR: str = (
|
||||
f"Version {VERSION} {(' (' + VERSION_BRANCH + ')') if VERSION_BRANCH else ''}"
|
||||
)
|
||||
|
||||
SPLASH_CLASSIC: str = "classic"
|
||||
SPLASH_GOO_GEARS: str = "goo_gears"
|
||||
SPLASH_95: str = "95"
|
||||
DEFAULT_SPLASH: str = SPLASH_GOO_GEARS
|
||||
DEFAULT_SPLASH = Splash.GOO_GEARS
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -41,11 +39,19 @@ class Splash:
|
||||
self.screen_width = screen_width
|
||||
self.ratio: float = device_ratio
|
||||
self.splash_screen: QSplashScreen | None = None
|
||||
self.splash_name: str = splash_name if splash_name else Splash.DEFAULT_SPLASH
|
||||
if not splash_name or splash_name == Splash.DEFAULT:
|
||||
self.splash_name: str = SplashScreen.DEFAULT_SPLASH
|
||||
elif splash_name == Splash.RANDOM:
|
||||
splash_list = list(Splash)
|
||||
splash_list.remove(Splash.DEFAULT)
|
||||
splash_list.remove(Splash.RANDOM)
|
||||
self.splash_name = random.choice(splash_list)
|
||||
else:
|
||||
self.splash_name = splash_name
|
||||
|
||||
def get_pixmap(self) -> QPixmap:
|
||||
"""Get the pixmap used for the splash screen."""
|
||||
pixmap: QPixmap | None = self.rm.get(f"splash_{self.splash_name}")
|
||||
pixmap: QPixmap | None = self.rm.get(f"splash_{self.splash_name}") # pyright: ignore[reportAssignmentType]
|
||||
if not pixmap:
|
||||
logger.error("[Splash] Splash screen not found:", splash_name=self.splash_name)
|
||||
pixmap = QPixmap(960, 540)
|
||||
@@ -55,10 +61,12 @@ class Splash:
|
||||
match painter.font().family():
|
||||
case "Segoe UI":
|
||||
point_size_scale = 0.75
|
||||
case _:
|
||||
pass
|
||||
|
||||
# TODO: Store any differing data elsewhere and load dynamically instead of hardcoding.
|
||||
match self.splash_name:
|
||||
case Splash.SPLASH_CLASSIC:
|
||||
case Splash.CLASSIC:
|
||||
# Copyright
|
||||
font = painter.font()
|
||||
font.setPointSize(math.floor(22 * point_size_scale))
|
||||
@@ -68,7 +76,7 @@ class Splash:
|
||||
painter.drawText(
|
||||
QRect(0, -50, 960, 540),
|
||||
int(Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignHCenter),
|
||||
Splash.COPYRIGHT_STR,
|
||||
SplashScreen.COPYRIGHT_STR,
|
||||
)
|
||||
# Version
|
||||
pen = QPen(QColor("#809782ff"))
|
||||
@@ -76,10 +84,10 @@ class Splash:
|
||||
painter.drawText(
|
||||
QRect(0, -25, 960, 540),
|
||||
int(Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignHCenter),
|
||||
Splash.VERSION_STR,
|
||||
SplashScreen.VERSION_STR,
|
||||
)
|
||||
|
||||
case Splash.SPLASH_GOO_GEARS:
|
||||
case Splash.GOO_GEARS:
|
||||
# Copyright
|
||||
font = painter.font()
|
||||
font.setPointSize(math.floor(22 * point_size_scale))
|
||||
@@ -88,7 +96,7 @@ class Splash:
|
||||
painter.setPen(pen)
|
||||
painter.drawText(
|
||||
QRect(40, 450, 960, 540),
|
||||
Splash.COPYRIGHT_STR,
|
||||
SplashScreen.COPYRIGHT_STR,
|
||||
)
|
||||
# Version
|
||||
font = painter.font()
|
||||
@@ -98,10 +106,10 @@ class Splash:
|
||||
painter.setPen(pen)
|
||||
painter.drawText(
|
||||
QRect(40, 475, 960, 540),
|
||||
Splash.VERSION_STR,
|
||||
SplashScreen.VERSION_STR,
|
||||
)
|
||||
|
||||
case Splash.SPLASH_95:
|
||||
case Splash.NINETY_FIVE:
|
||||
# Copyright
|
||||
font = QFont()
|
||||
font.setFamily("Times")
|
||||
@@ -114,7 +122,7 @@ class Splash:
|
||||
painter.drawText(
|
||||
QRect(88, -25, 960, 540),
|
||||
int(Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignLeft),
|
||||
Splash.COPYRIGHT_STR,
|
||||
SplashScreen.COPYRIGHT_STR,
|
||||
)
|
||||
# Version
|
||||
font.setPointSize(math.floor(22 * point_size_scale))
|
||||
@@ -124,7 +132,7 @@ class Splash:
|
||||
painter.drawText(
|
||||
QRect(-30, 25, 960, 540),
|
||||
int(Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignRight),
|
||||
Splash.VERSION_STR,
|
||||
SplashScreen.VERSION_STR,
|
||||
)
|
||||
|
||||
case _:
|
||||
|
||||
@@ -6,6 +6,8 @@ from typing import Any
|
||||
import structlog
|
||||
import ujson
|
||||
|
||||
from tagstudio.qt.mnemonics import remove_mnemonic_marker
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
DEFAULT_TRANSLATION = "en"
|
||||
@@ -61,9 +63,7 @@ class Translator:
|
||||
self._strings = self.__get_translation_dict(lang)
|
||||
if system() == "Darwin":
|
||||
for k, v in self._strings.items():
|
||||
self._strings[k] = (
|
||||
v.replace("&&", "<ESC_AMP>").replace("&", "", 1).replace("<ESC_AMP>", "&&")
|
||||
)
|
||||
self._strings[k] = remove_mnemonic_marker(v)
|
||||
|
||||
def __format(self, text: str, **kwargs) -> str:
|
||||
try:
|
||||
|
||||
@@ -45,7 +45,6 @@ from PySide6.QtWidgets import (
|
||||
QScrollArea,
|
||||
)
|
||||
|
||||
# this import has side-effect of import PySide resources
|
||||
import tagstudio.qt.resources_rc # noqa: F401
|
||||
from tagstudio.core.constants import TAG_ARCHIVED, TAG_FAVORITE, VERSION, VERSION_BRANCH
|
||||
from tagstudio.core.driver import DriverMixin
|
||||
@@ -67,6 +66,9 @@ from tagstudio.core.utils.refresh_dir import RefreshDirTracker
|
||||
from tagstudio.core.utils.types import unwrap
|
||||
from tagstudio.core.utils.web import strip_web_protocol
|
||||
from tagstudio.qt.cache_manager import CacheManager
|
||||
|
||||
# this import has side-effect of import PySide resources
|
||||
from tagstudio.qt.controller.fix_ignored_modal_controller import FixIgnoredEntriesModal
|
||||
from tagstudio.qt.controller.widgets.ignore_modal_controller import IgnoreModal
|
||||
from tagstudio.qt.controller.widgets.library_info_window_controller import LibraryInfoWindow
|
||||
from tagstudio.qt.helpers.custom_runnable import CustomRunnable
|
||||
@@ -87,7 +89,7 @@ from tagstudio.qt.modals.tag_database import TagDatabasePanel
|
||||
from tagstudio.qt.modals.tag_search import TagSearchModal
|
||||
from tagstudio.qt.platform_strings import trash_term
|
||||
from tagstudio.qt.resource_manager import ResourceManager
|
||||
from tagstudio.qt.splash import Splash
|
||||
from tagstudio.qt.splash import SplashScreen
|
||||
from tagstudio.qt.translations import Translations
|
||||
from tagstudio.qt.widgets.item_thumb import BadgeType, ItemThumb
|
||||
from tagstudio.qt.widgets.migration_modal import JsonMigrationModal
|
||||
@@ -178,6 +180,7 @@ class QtDriver(DriverMixin, QObject):
|
||||
folders_modal: FoldersToTagsModal
|
||||
about_modal: AboutModal
|
||||
unlinked_modal: FixUnlinkedEntriesModal
|
||||
ignored_modal: FixIgnoredEntriesModal
|
||||
dupe_modal: FixDupeFilesModal
|
||||
library_info_window: LibraryInfoWindow
|
||||
|
||||
@@ -322,10 +325,10 @@ class QtDriver(DriverMixin, QObject):
|
||||
self.main_window.dragMoveEvent = self.drag_move_event
|
||||
self.main_window.dropEvent = self.drop_event
|
||||
|
||||
self.splash: Splash = Splash(
|
||||
self.splash: SplashScreen = SplashScreen(
|
||||
resource_manager=self.rm,
|
||||
screen_width=QGuiApplication.primaryScreen().geometry().width(),
|
||||
splash_name="", # TODO: Get splash name from config
|
||||
splash_name=self.settings.splash,
|
||||
device_ratio=self.main_window.devicePixelRatio(),
|
||||
)
|
||||
self.splash.show()
|
||||
@@ -501,6 +504,15 @@ class QtDriver(DriverMixin, QObject):
|
||||
create_fix_unlinked_entries_modal
|
||||
)
|
||||
|
||||
def create_ignored_entries_modal():
|
||||
if not hasattr(self, "ignored_modal"):
|
||||
self.ignored_modal = FixIgnoredEntriesModal(self.lib, self)
|
||||
self.ignored_modal.show()
|
||||
|
||||
self.main_window.menu_bar.fix_ignored_entries_action.triggered.connect(
|
||||
create_ignored_entries_modal
|
||||
)
|
||||
|
||||
def create_dupe_files_modal():
|
||||
if not hasattr(self, "dupe_modal"):
|
||||
self.dupe_modal = FixDupeFilesModal(self.lib, self)
|
||||
@@ -751,6 +763,7 @@ class QtDriver(DriverMixin, QObject):
|
||||
self.main_window.menu_bar.ignore_modal_action.setEnabled(False)
|
||||
self.main_window.menu_bar.new_tag_action.setEnabled(False)
|
||||
self.main_window.menu_bar.fix_unlinked_entries_action.setEnabled(False)
|
||||
self.main_window.menu_bar.fix_ignored_entries_action.setEnabled(False)
|
||||
self.main_window.menu_bar.fix_dupe_files_action.setEnabled(False)
|
||||
self.main_window.menu_bar.clear_thumb_cache_action.setEnabled(False)
|
||||
self.main_window.menu_bar.folders_to_tags_action.setEnabled(False)
|
||||
@@ -1745,6 +1758,7 @@ class QtDriver(DriverMixin, QObject):
|
||||
self.main_window.menu_bar.ignore_modal_action.setEnabled(True)
|
||||
self.main_window.menu_bar.new_tag_action.setEnabled(True)
|
||||
self.main_window.menu_bar.fix_unlinked_entries_action.setEnabled(True)
|
||||
self.main_window.menu_bar.fix_ignored_entries_action.setEnabled(True)
|
||||
self.main_window.menu_bar.fix_dupe_files_action.setEnabled(True)
|
||||
self.main_window.menu_bar.clear_thumb_cache_action.setEnabled(True)
|
||||
self.main_window.menu_bar.folders_to_tags_action.setEnabled(True)
|
||||
|
||||
67
src/tagstudio/qt/view/widgets/fix_ignored_modal_view.py
Normal file
67
src/tagstudio/qt/view/widgets/fix_ignored_modal_view.py
Normal file
@@ -0,0 +1,67 @@
|
||||
# Copyright (C) 2025 Travis Abendshien (CyanVoxel).
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
|
||||
from typing import TYPE_CHECKING, override
|
||||
|
||||
from PySide6 import QtCore, QtGui
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtWidgets import QHBoxLayout, QLabel, QPushButton, QVBoxLayout, QWidget
|
||||
|
||||
from tagstudio.core.library.alchemy.library import Library
|
||||
from tagstudio.qt.translations import Translations
|
||||
|
||||
# Only import for type checking/autocompletion, will not be imported at runtime.
|
||||
if TYPE_CHECKING:
|
||||
from tagstudio.qt.ts_qt import QtDriver
|
||||
|
||||
|
||||
class FixIgnoredEntriesModalView(QWidget):
|
||||
def __init__(self, library: "Library", driver: "QtDriver"):
|
||||
super().__init__()
|
||||
self.lib = library
|
||||
self.driver = driver
|
||||
|
||||
self.setWindowTitle(Translations["entries.ignored.title"])
|
||||
self.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||
self.setMinimumSize(400, 300)
|
||||
self.root_layout = QVBoxLayout(self)
|
||||
self.root_layout.setContentsMargins(6, 6, 6, 6)
|
||||
|
||||
self.ignored_desc_widget = QLabel(Translations["entries.ignored.description"])
|
||||
self.ignored_desc_widget.setObjectName("ignoredDescriptionLabel")
|
||||
self.ignored_desc_widget.setWordWrap(True)
|
||||
self.ignored_desc_widget.setStyleSheet("text-align:left;")
|
||||
|
||||
self.ignored_count_label = QLabel()
|
||||
self.ignored_count_label.setObjectName("ignoredCountLabel")
|
||||
self.ignored_count_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
self.refresh_ignored_button = QPushButton(Translations["entries.generic.refresh_alt"])
|
||||
self.remove_button = QPushButton(Translations["entries.ignored.remove_alt"])
|
||||
|
||||
self.button_container = QWidget()
|
||||
self.button_layout = QHBoxLayout(self.button_container)
|
||||
self.button_layout.setContentsMargins(6, 6, 6, 6)
|
||||
self.button_layout.addStretch(1)
|
||||
|
||||
self.done_button = QPushButton(Translations["generic.done_alt"])
|
||||
self.done_button.setDefault(True)
|
||||
self.button_layout.addWidget(self.done_button)
|
||||
|
||||
self.root_layout.addWidget(self.ignored_count_label)
|
||||
self.root_layout.addWidget(self.ignored_desc_widget)
|
||||
self.root_layout.addWidget(self.refresh_ignored_button)
|
||||
self.root_layout.addWidget(self.remove_button)
|
||||
self.root_layout.addStretch(1)
|
||||
self.root_layout.addStretch(2)
|
||||
self.root_layout.addWidget(self.button_container)
|
||||
|
||||
@override
|
||||
def keyPressEvent(self, event: QtGui.QKeyEvent) -> None: # noqa N802
|
||||
if event.key() == QtCore.Qt.Key.Key_Escape:
|
||||
self.done_button.click()
|
||||
else: # Other key presses
|
||||
pass
|
||||
return super().keyPressEvent(event)
|
||||
@@ -3,10 +3,15 @@
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
|
||||
import math
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from PIL import Image, ImageQt
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtGui import QPixmap
|
||||
from PySide6.QtWidgets import (
|
||||
QFrame,
|
||||
QGraphicsOpacityEffect,
|
||||
QGridLayout,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
@@ -17,11 +22,13 @@ from PySide6.QtWidgets import (
|
||||
QWidget,
|
||||
)
|
||||
|
||||
from tagstudio.core.library.alchemy.library import Library
|
||||
from tagstudio.qt.helpers.color_overlay import theme_fg_overlay
|
||||
from tagstudio.qt.platform_strings import open_file_str
|
||||
from tagstudio.qt.translations import Translations
|
||||
|
||||
# Only import for type checking/autocompletion, will not be imported at runtime.
|
||||
if TYPE_CHECKING:
|
||||
from tagstudio.core.library.alchemy.library import Library
|
||||
from tagstudio.qt.ts_qt import QtDriver
|
||||
|
||||
|
||||
@@ -32,11 +39,12 @@ class LibraryInfoWindowView(QWidget):
|
||||
self.driver = driver
|
||||
|
||||
self.setWindowTitle("Library Information")
|
||||
self.setMinimumSize(400, 300)
|
||||
self.setMinimumSize(800, 480)
|
||||
self.root_layout = QVBoxLayout(self)
|
||||
self.root_layout.setContentsMargins(6, 6, 6, 6)
|
||||
|
||||
row_height: int = 22
|
||||
icon_margin: int = 4
|
||||
cell_alignment: Qt.AlignmentFlag = (
|
||||
Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter
|
||||
)
|
||||
@@ -49,11 +57,12 @@ class LibraryInfoWindowView(QWidget):
|
||||
self.body_widget = QWidget()
|
||||
self.body_layout = QHBoxLayout(self.body_widget)
|
||||
self.body_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.body_layout.setSpacing(0)
|
||||
self.body_layout.setSpacing(6)
|
||||
|
||||
# Statistics -----------------------------------------------------------
|
||||
self.stats_widget = QWidget()
|
||||
self.stats_layout = QVBoxLayout(self.stats_widget)
|
||||
self.stats_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
||||
self.stats_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.stats_layout.setSpacing(12)
|
||||
|
||||
@@ -67,16 +76,17 @@ class LibraryInfoWindowView(QWidget):
|
||||
self.stats_grid_layout.setColumnMinimumWidth(1, 12)
|
||||
self.stats_grid_layout.setColumnMinimumWidth(3, 12)
|
||||
|
||||
self.entries_row: int = 0
|
||||
self.tags_row: int = 1
|
||||
self.fields_row: int = 2
|
||||
self.namespaces_row: int = 3
|
||||
self.colors_row: int = 4
|
||||
self.macros_row: int = 5
|
||||
self.stats_entries_row: int = 0
|
||||
self.stats_tags_row: int = 1
|
||||
self.stats_fields_row: int = 2
|
||||
self.stats_namespaces_row: int = 3
|
||||
self.stats_colors_row: int = 4
|
||||
self.stats_macros_row: int = 5
|
||||
|
||||
self.labels_col: int = 0
|
||||
self.values_col: int = 2
|
||||
self.buttons_col: int = 4
|
||||
# NOTE: Alternating rows for visual padding
|
||||
self.stats_labels_col: int = 0
|
||||
self.stats_values_col: int = 2
|
||||
self.stats_buttons_col: int = 4
|
||||
|
||||
self.entries_label: QLabel = QLabel(Translations["library_info.stats.entries"])
|
||||
self.entries_label.setAlignment(cell_alignment)
|
||||
@@ -91,21 +101,43 @@ class LibraryInfoWindowView(QWidget):
|
||||
self.macros_label: QLabel = QLabel(Translations["library_info.stats.macros"])
|
||||
self.macros_label.setAlignment(cell_alignment)
|
||||
|
||||
self.stats_grid_layout.addWidget(self.entries_label, self.entries_row, self.labels_col)
|
||||
self.stats_grid_layout.addWidget(self.tags_label, self.tags_row, self.labels_col)
|
||||
self.stats_grid_layout.addWidget(self.fields_label, self.fields_row, self.labels_col)
|
||||
self.stats_grid_layout.addWidget(
|
||||
self.namespaces_label, self.namespaces_row, self.labels_col
|
||||
self.entries_label,
|
||||
self.stats_entries_row,
|
||||
self.stats_labels_col,
|
||||
)
|
||||
self.stats_grid_layout.addWidget(
|
||||
self.tags_label,
|
||||
self.stats_tags_row,
|
||||
self.stats_labels_col,
|
||||
)
|
||||
self.stats_grid_layout.addWidget(
|
||||
self.fields_label,
|
||||
self.stats_fields_row,
|
||||
self.stats_labels_col,
|
||||
)
|
||||
self.stats_grid_layout.addWidget(
|
||||
self.namespaces_label,
|
||||
self.stats_namespaces_row,
|
||||
self.stats_labels_col,
|
||||
)
|
||||
self.stats_grid_layout.addWidget(
|
||||
self.colors_label,
|
||||
self.stats_colors_row,
|
||||
self.stats_labels_col,
|
||||
)
|
||||
self.stats_grid_layout.addWidget(
|
||||
self.macros_label,
|
||||
self.stats_macros_row,
|
||||
self.stats_labels_col,
|
||||
)
|
||||
self.stats_grid_layout.addWidget(self.colors_label, self.colors_row, self.labels_col)
|
||||
self.stats_grid_layout.addWidget(self.macros_label, self.macros_row, self.labels_col)
|
||||
|
||||
self.stats_grid_layout.setRowMinimumHeight(self.entries_row, row_height)
|
||||
self.stats_grid_layout.setRowMinimumHeight(self.tags_row, row_height)
|
||||
self.stats_grid_layout.setRowMinimumHeight(self.fields_row, row_height)
|
||||
self.stats_grid_layout.setRowMinimumHeight(self.namespaces_row, row_height)
|
||||
self.stats_grid_layout.setRowMinimumHeight(self.colors_row, row_height)
|
||||
self.stats_grid_layout.setRowMinimumHeight(self.macros_row, row_height)
|
||||
self.stats_grid_layout.setRowMinimumHeight(self.stats_entries_row, row_height)
|
||||
self.stats_grid_layout.setRowMinimumHeight(self.stats_tags_row, row_height)
|
||||
self.stats_grid_layout.setRowMinimumHeight(self.stats_fields_row, row_height)
|
||||
self.stats_grid_layout.setRowMinimumHeight(self.stats_namespaces_row, row_height)
|
||||
self.stats_grid_layout.setRowMinimumHeight(self.stats_colors_row, row_height)
|
||||
self.stats_grid_layout.setRowMinimumHeight(self.stats_macros_row, row_height)
|
||||
|
||||
self.entry_count_label: QLabel = QLabel()
|
||||
self.entry_count_label.setAlignment(cell_alignment)
|
||||
@@ -120,21 +152,49 @@ class LibraryInfoWindowView(QWidget):
|
||||
self.macros_count_label: QLabel = QLabel()
|
||||
self.macros_count_label.setAlignment(cell_alignment)
|
||||
|
||||
self.stats_grid_layout.addWidget(self.entry_count_label, self.entries_row, self.values_col)
|
||||
self.stats_grid_layout.addWidget(self.tag_count_label, self.tags_row, self.values_col)
|
||||
self.stats_grid_layout.addWidget(self.field_count_label, self.fields_row, self.values_col)
|
||||
self.stats_grid_layout.addWidget(
|
||||
self.namespaces_count_label, self.namespaces_row, self.values_col
|
||||
self.entry_count_label,
|
||||
self.stats_entries_row,
|
||||
self.stats_values_col,
|
||||
)
|
||||
self.stats_grid_layout.addWidget(
|
||||
self.tag_count_label,
|
||||
self.stats_tags_row,
|
||||
self.stats_values_col,
|
||||
)
|
||||
self.stats_grid_layout.addWidget(
|
||||
self.field_count_label,
|
||||
self.stats_fields_row,
|
||||
self.stats_values_col,
|
||||
)
|
||||
self.stats_grid_layout.addWidget(
|
||||
self.namespaces_count_label,
|
||||
self.stats_namespaces_row,
|
||||
self.stats_values_col,
|
||||
)
|
||||
self.stats_grid_layout.addWidget(
|
||||
self.color_count_label,
|
||||
self.stats_colors_row,
|
||||
self.stats_values_col,
|
||||
)
|
||||
self.stats_grid_layout.addWidget(
|
||||
self.macros_count_label,
|
||||
self.stats_macros_row,
|
||||
self.stats_values_col,
|
||||
)
|
||||
self.stats_grid_layout.addWidget(self.color_count_label, self.colors_row, self.values_col)
|
||||
self.stats_grid_layout.addWidget(self.macros_count_label, self.macros_row, self.values_col)
|
||||
|
||||
self.manage_tags_button = QPushButton(Translations["edit.tag_manager"])
|
||||
self.manage_colors_button = QPushButton(Translations["color_manager.title"])
|
||||
|
||||
self.stats_grid_layout.addWidget(self.manage_tags_button, self.tags_row, self.buttons_col)
|
||||
self.stats_grid_layout.addWidget(
|
||||
self.manage_colors_button, self.colors_row, self.buttons_col
|
||||
self.manage_tags_button,
|
||||
self.stats_tags_row,
|
||||
self.stats_buttons_col,
|
||||
)
|
||||
self.stats_grid_layout.addWidget(
|
||||
self.manage_colors_button,
|
||||
self.stats_colors_row,
|
||||
self.stats_buttons_col,
|
||||
)
|
||||
|
||||
self.stats_layout.addWidget(self.stats_label)
|
||||
@@ -148,7 +208,246 @@ class LibraryInfoWindowView(QWidget):
|
||||
QSpacerItem(0, 0, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
|
||||
)
|
||||
|
||||
# Buttons
|
||||
# Vertical Separator
|
||||
self.vertical_sep = QFrame()
|
||||
self.vertical_sep.setFrameShape(QFrame.Shape.VLine)
|
||||
self.vertical_sep.setFrameShadow(QFrame.Shadow.Plain)
|
||||
opacity_effect_vert_sep = QGraphicsOpacityEffect(self)
|
||||
opacity_effect_vert_sep.setOpacity(0.1)
|
||||
self.vertical_sep.setGraphicsEffect(opacity_effect_vert_sep)
|
||||
self.body_layout.addWidget(self.vertical_sep)
|
||||
|
||||
# Cleanup --------------------------------------------------------------
|
||||
self.cleanup_widget = QWidget()
|
||||
self.cleanup_layout = QVBoxLayout(self.cleanup_widget)
|
||||
self.cleanup_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
||||
self.cleanup_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.cleanup_layout.setSpacing(12)
|
||||
|
||||
self.cleanup_label = QLabel(f"<h3>{Translations['library_info.cleanup']}</h3>")
|
||||
self.cleanup_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
self.cleanup_grid: QWidget = QWidget()
|
||||
self.cleanup_grid_layout: QGridLayout = QGridLayout(self.cleanup_grid)
|
||||
self.cleanup_grid_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.cleanup_grid_layout.setSpacing(0)
|
||||
self.cleanup_grid_layout.setColumnMinimumWidth(1, 12)
|
||||
self.cleanup_grid_layout.setColumnMinimumWidth(3, 6)
|
||||
self.cleanup_grid_layout.setColumnMinimumWidth(5, 6)
|
||||
|
||||
self.cleanup_layout.addWidget(self.cleanup_label)
|
||||
self.cleanup_layout.addWidget(self.cleanup_grid)
|
||||
|
||||
self.cleanup_unlinked_row: int = 0
|
||||
self.cleanup_ignored_row: int = 1
|
||||
self.cleanup_dupe_files_row: int = 2
|
||||
self.cleanup_section_break_row: int = 3
|
||||
self.cleanup_legacy_json_row: int = 4
|
||||
self.cleanup_backups_row: int = 5
|
||||
|
||||
# NOTE: Alternating rows for visual padding
|
||||
self.cleanup_labels_col: int = 0
|
||||
self.cleanup_values_col: int = 2
|
||||
self.cleanup_icons_col: int = 4
|
||||
self.cleanup_buttons_col: int = 6
|
||||
|
||||
# Horizontal Separator
|
||||
self.horizontal_sep = QFrame()
|
||||
self.horizontal_sep.setFrameShape(QFrame.Shape.HLine)
|
||||
self.horizontal_sep.setFrameShadow(QFrame.Shadow.Plain)
|
||||
self.horizontal_sep.setFixedHeight(row_height)
|
||||
opacity_effect_hor_sep = QGraphicsOpacityEffect(self)
|
||||
opacity_effect_hor_sep.setOpacity(0.1)
|
||||
self.horizontal_sep.setGraphicsEffect(opacity_effect_hor_sep)
|
||||
self.cleanup_grid_layout.addWidget(
|
||||
self.horizontal_sep,
|
||||
self.cleanup_section_break_row,
|
||||
self.cleanup_labels_col,
|
||||
1,
|
||||
7,
|
||||
Qt.AlignmentFlag.AlignVCenter,
|
||||
)
|
||||
|
||||
self.unlinked_icon = QLabel()
|
||||
unlinked_image: Image.Image = self.driver.rm.get("unlinked_stat") # pyright: ignore[reportAssignmentType]
|
||||
unlinked_pixmap = QPixmap.fromImage(ImageQt.ImageQt(unlinked_image))
|
||||
unlinked_pixmap.setDevicePixelRatio(self.devicePixelRatio())
|
||||
unlinked_pixmap = unlinked_pixmap.scaledToWidth(
|
||||
math.floor((row_height - icon_margin) * self.devicePixelRatio()),
|
||||
Qt.TransformationMode.SmoothTransformation,
|
||||
)
|
||||
self.unlinked_icon.setPixmap(unlinked_pixmap)
|
||||
|
||||
self.ignored_icon = QLabel()
|
||||
ignored_image: Image.Image = self.driver.rm.get("ignored_stat") # pyright: ignore[reportAssignmentType]
|
||||
ignored_pixmap = QPixmap.fromImage(ImageQt.ImageQt(ignored_image))
|
||||
ignored_pixmap.setDevicePixelRatio(self.devicePixelRatio())
|
||||
ignored_pixmap = ignored_pixmap.scaledToWidth(
|
||||
math.floor((row_height - icon_margin) * self.devicePixelRatio()),
|
||||
Qt.TransformationMode.SmoothTransformation,
|
||||
)
|
||||
self.ignored_icon.setPixmap(ignored_pixmap)
|
||||
|
||||
self.dupe_file_icon = QLabel()
|
||||
dupe_file_image: Image.Image = self.driver.rm.get("dupe_file_stat") # pyright: ignore[reportAssignmentType]
|
||||
dupe_file_pixmap = QPixmap.fromImage(
|
||||
ImageQt.ImageQt(theme_fg_overlay(dupe_file_image, use_alpha=False))
|
||||
)
|
||||
dupe_file_pixmap.setDevicePixelRatio(self.devicePixelRatio())
|
||||
dupe_file_pixmap = dupe_file_pixmap.scaledToWidth(
|
||||
math.floor((row_height - icon_margin) * self.devicePixelRatio()),
|
||||
Qt.TransformationMode.SmoothTransformation,
|
||||
)
|
||||
self.dupe_file_icon.setPixmap(dupe_file_pixmap)
|
||||
|
||||
self.cleanup_grid_layout.addWidget(
|
||||
self.unlinked_icon,
|
||||
self.cleanup_unlinked_row,
|
||||
self.cleanup_icons_col,
|
||||
)
|
||||
self.cleanup_grid_layout.addWidget(
|
||||
self.ignored_icon,
|
||||
self.cleanup_ignored_row,
|
||||
self.cleanup_icons_col,
|
||||
)
|
||||
self.cleanup_grid_layout.addWidget(
|
||||
self.dupe_file_icon,
|
||||
self.cleanup_dupe_files_row,
|
||||
self.cleanup_icons_col,
|
||||
)
|
||||
|
||||
self.unlinked_label: QLabel = QLabel(Translations["library_info.cleanup.unlinked"])
|
||||
self.unlinked_label.setAlignment(cell_alignment)
|
||||
self.ignored_label: QLabel = QLabel(Translations["library_info.cleanup.ignored"])
|
||||
self.ignored_label.setAlignment(cell_alignment)
|
||||
self.dupe_files_label: QLabel = QLabel(Translations["library_info.cleanup.dupe_files"])
|
||||
self.dupe_files_label.setAlignment(cell_alignment)
|
||||
self.legacy_json_label: QLabel = QLabel(Translations["library_info.cleanup.legacy_json"])
|
||||
self.legacy_json_label.setAlignment(cell_alignment)
|
||||
self.backups_label: QLabel = QLabel(Translations["library_info.cleanup.backups"])
|
||||
self.backups_label.setAlignment(cell_alignment)
|
||||
|
||||
self.cleanup_grid_layout.addWidget(
|
||||
self.unlinked_label,
|
||||
self.cleanup_unlinked_row,
|
||||
self.cleanup_labels_col,
|
||||
)
|
||||
self.cleanup_grid_layout.addWidget(
|
||||
self.ignored_label,
|
||||
self.cleanup_ignored_row,
|
||||
self.cleanup_labels_col,
|
||||
)
|
||||
self.cleanup_grid_layout.addWidget(
|
||||
self.dupe_files_label,
|
||||
self.cleanup_dupe_files_row,
|
||||
self.cleanup_labels_col,
|
||||
)
|
||||
self.cleanup_grid_layout.addWidget(
|
||||
self.legacy_json_label,
|
||||
self.cleanup_legacy_json_row,
|
||||
self.cleanup_labels_col,
|
||||
)
|
||||
self.cleanup_grid_layout.addWidget(
|
||||
self.backups_label,
|
||||
self.cleanup_backups_row,
|
||||
self.cleanup_labels_col,
|
||||
)
|
||||
|
||||
self.cleanup_grid_layout.setRowMinimumHeight(self.cleanup_unlinked_row, row_height)
|
||||
self.cleanup_grid_layout.setRowMinimumHeight(self.cleanup_ignored_row, row_height)
|
||||
self.cleanup_grid_layout.setRowMinimumHeight(self.cleanup_dupe_files_row, row_height)
|
||||
self.cleanup_grid_layout.setRowMinimumHeight(self.cleanup_legacy_json_row, row_height)
|
||||
self.cleanup_grid_layout.setRowMinimumHeight(self.cleanup_backups_row, row_height)
|
||||
|
||||
self.unlinked_count_label: QLabel = QLabel()
|
||||
self.unlinked_count_label.setAlignment(cell_alignment)
|
||||
self.ignored_count_label: QLabel = QLabel()
|
||||
self.ignored_count_label.setAlignment(cell_alignment)
|
||||
self.dupe_files_count_label: QLabel = QLabel()
|
||||
self.dupe_files_count_label.setAlignment(cell_alignment)
|
||||
self.legacy_json_status_label: QLabel = QLabel()
|
||||
self.legacy_json_status_label.setAlignment(cell_alignment)
|
||||
self.backups_count_label: QLabel = QLabel()
|
||||
self.backups_count_label.setAlignment(cell_alignment)
|
||||
|
||||
self.cleanup_grid_layout.addWidget(
|
||||
self.unlinked_count_label,
|
||||
self.cleanup_unlinked_row,
|
||||
self.cleanup_values_col,
|
||||
)
|
||||
self.cleanup_grid_layout.addWidget(
|
||||
self.ignored_count_label,
|
||||
self.cleanup_ignored_row,
|
||||
self.cleanup_values_col,
|
||||
)
|
||||
self.cleanup_grid_layout.addWidget(
|
||||
self.dupe_files_count_label,
|
||||
self.cleanup_dupe_files_row,
|
||||
self.cleanup_values_col,
|
||||
)
|
||||
self.cleanup_grid_layout.addWidget(
|
||||
self.legacy_json_status_label,
|
||||
self.cleanup_legacy_json_row,
|
||||
self.cleanup_values_col,
|
||||
)
|
||||
self.cleanup_grid_layout.addWidget(
|
||||
self.backups_count_label,
|
||||
self.cleanup_backups_row,
|
||||
self.cleanup_values_col,
|
||||
)
|
||||
|
||||
self.fix_unlinked_entries = QPushButton(Translations["menu.tools.fix_unlinked_entries"])
|
||||
self.fix_ignored_entries = QPushButton(Translations["menu.tools.fix_ignored_entries"])
|
||||
self.fix_dupe_files = QPushButton(Translations["menu.tools.fix_duplicate_files"])
|
||||
self.view_legacy_json_file = QPushButton(open_file_str())
|
||||
self.open_backups_folder = QPushButton(Translations["menu.file.open_backups_folder"])
|
||||
|
||||
self.cleanup_grid_layout.addWidget(
|
||||
self.fix_unlinked_entries,
|
||||
self.cleanup_unlinked_row,
|
||||
self.cleanup_buttons_col,
|
||||
)
|
||||
self.cleanup_grid_layout.addWidget(
|
||||
self.fix_ignored_entries,
|
||||
self.cleanup_ignored_row,
|
||||
self.cleanup_buttons_col,
|
||||
)
|
||||
self.cleanup_grid_layout.addWidget(
|
||||
self.fix_dupe_files,
|
||||
self.cleanup_dupe_files_row,
|
||||
self.cleanup_buttons_col,
|
||||
)
|
||||
self.cleanup_grid_layout.addWidget(
|
||||
self.view_legacy_json_file,
|
||||
self.cleanup_legacy_json_row,
|
||||
self.cleanup_buttons_col,
|
||||
)
|
||||
self.cleanup_grid_layout.addWidget(
|
||||
self.open_backups_folder,
|
||||
self.cleanup_backups_row,
|
||||
self.cleanup_buttons_col,
|
||||
)
|
||||
|
||||
self.body_layout.addSpacerItem(
|
||||
QSpacerItem(0, 0, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
|
||||
)
|
||||
self.body_layout.addWidget(self.cleanup_widget)
|
||||
self.body_layout.addSpacerItem(
|
||||
QSpacerItem(0, 0, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
|
||||
)
|
||||
|
||||
# Details --------------------------------------------------------------
|
||||
self.details_container = QWidget()
|
||||
self.details_layout = QHBoxLayout(self.details_container)
|
||||
self.details_layout.setContentsMargins(6, 0, 6, 0)
|
||||
opacity_effect_details = QGraphicsOpacityEffect(self)
|
||||
opacity_effect_details.setOpacity(0.5)
|
||||
|
||||
self.version_label = QLabel()
|
||||
self.version_label.setGraphicsEffect(opacity_effect_details)
|
||||
self.details_layout.addWidget(self.version_label)
|
||||
|
||||
# Buttons --------------------------------------------------------------
|
||||
self.button_container = QWidget()
|
||||
self.button_layout = QHBoxLayout(self.button_container)
|
||||
self.button_layout.setContentsMargins(6, 6, 6, 6)
|
||||
@@ -162,4 +461,5 @@ class LibraryInfoWindowView(QWidget):
|
||||
self.root_layout.addWidget(self.body_widget)
|
||||
self.root_layout.addStretch(1)
|
||||
self.root_layout.addStretch(2)
|
||||
self.root_layout.addWidget(self.details_container)
|
||||
self.root_layout.addWidget(self.button_container)
|
||||
|
||||
@@ -169,9 +169,11 @@ class FieldContainers(QWidget):
|
||||
"Character" -> "Johnny Bravo",
|
||||
"TV" -> Johnny Bravo"
|
||||
"""
|
||||
hierarchy_tags = self.lib.get_tag_hierarchy(t.id for t in tags)
|
||||
loop_cutoff = 1024 # Used for stopping the while loop
|
||||
|
||||
hierarchy_tags = self.lib.get_tag_hierarchy(t.id for t in tags)
|
||||
categories: dict[Tag | None, set[Tag]] = {None: set()}
|
||||
|
||||
for tag in hierarchy_tags.values():
|
||||
if tag.is_category:
|
||||
categories[tag] = set()
|
||||
@@ -179,7 +181,15 @@ class FieldContainers(QWidget):
|
||||
tag = hierarchy_tags[tag.id]
|
||||
has_category_parent = False
|
||||
parent_tags = tag.parent_tags
|
||||
|
||||
loop_counter = 0
|
||||
while len(parent_tags) > 0:
|
||||
# NOTE: This is for preventing infinite loops in the event a tag is parented
|
||||
# to itself cyclically.
|
||||
loop_counter += 1
|
||||
if loop_counter >= loop_cutoff:
|
||||
break
|
||||
|
||||
grandparent_tags: set[Tag] = set()
|
||||
for parent_tag in parent_tags:
|
||||
if parent_tag in categories:
|
||||
@@ -187,6 +197,7 @@ class FieldContainers(QWidget):
|
||||
has_category_parent = True
|
||||
grandparent_tags.update(parent_tag.parent_tags)
|
||||
parent_tags = grandparent_tags
|
||||
|
||||
if tag.is_category:
|
||||
categories[tag].add(tag)
|
||||
elif not has_category_parent:
|
||||
|
||||
@@ -16,6 +16,7 @@ from warnings import catch_warnings
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
import pillow_avif # noqa: F401 # pyright: ignore[reportUnusedImport]
|
||||
import rawpy
|
||||
import srctools
|
||||
import structlog
|
||||
@@ -33,7 +34,7 @@ from PIL import (
|
||||
UnidentifiedImageError,
|
||||
)
|
||||
from PIL.Image import DecompressionBombError
|
||||
from pillow_heif import register_avif_opener, register_heif_opener
|
||||
from pillow_heif import register_heif_opener
|
||||
from PySide6.QtCore import (
|
||||
QBuffer,
|
||||
QFile,
|
||||
@@ -58,6 +59,7 @@ from tagstudio.core.library.ignore import Ignore
|
||||
from tagstudio.core.media_types import MediaCategories, MediaType
|
||||
from tagstudio.core.palette import UI_COLORS, ColorType, UiColor, get_ui_color
|
||||
from tagstudio.core.utils.encoding import detect_char_encoding
|
||||
from tagstudio.core.utils.types import unwrap
|
||||
from tagstudio.qt.helpers.blender_thumbnailer import blend_thumb
|
||||
from tagstudio.qt.helpers.color_overlay import theme_fg_overlay
|
||||
from tagstudio.qt.helpers.file_tester import is_readable_video
|
||||
@@ -79,7 +81,6 @@ os.environ["OPENCV_IO_ENABLE_OPENEXR"] = "1"
|
||||
logger = structlog.get_logger(__name__)
|
||||
Image.MAX_IMAGE_PIXELS = None
|
||||
register_heif_opener()
|
||||
register_avif_opener()
|
||||
|
||||
try:
|
||||
import pillow_jxl # noqa: F401 # pyright: ignore[reportUnusedImport]
|
||||
@@ -1436,7 +1437,9 @@ class ThumbRenderer(QObject):
|
||||
if (
|
||||
image
|
||||
and Ignore.compiled_patterns
|
||||
and Ignore.compiled_patterns.match(filepath.relative_to(self.lib.library_dir))
|
||||
and Ignore.compiled_patterns.match(
|
||||
filepath.relative_to(unwrap(self.lib.library_dir))
|
||||
)
|
||||
):
|
||||
image = render_ignored((adj_size, adj_size), pixel_ratio, image)
|
||||
except TypeError:
|
||||
|
||||
BIN
src/tagstudio/resources/qt/images/dupe_file_stat.png
Normal file
BIN
src/tagstudio/resources/qt/images/dupe_file_stat.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.9 KiB |
BIN
src/tagstudio/resources/qt/images/ignored_stat.png
Normal file
BIN
src/tagstudio/resources/qt/images/ignored_stat.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.1 KiB |
BIN
src/tagstudio/resources/qt/images/splash/95.png
Normal file
BIN
src/tagstudio/resources/qt/images/splash/95.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 116 KiB |
BIN
src/tagstudio/resources/qt/images/unlinked_stat.png
Normal file
BIN
src/tagstudio/resources/qt/images/unlinked_stat.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.1 KiB |
@@ -12,17 +12,17 @@
|
||||
"color.color_border": "Benutze Sekundärfarbe für die Umrandung",
|
||||
"color.confirm_delete": "Soll die Farbe \"{color_name}\" wirklich gelöscht werden?",
|
||||
"color.delete": "Tag löschen",
|
||||
"color.import_pack": "Farb-Paket importieren",
|
||||
"color.import_pack": "Farbpaket importieren",
|
||||
"color.name": "Name",
|
||||
"color.namespace.delete.prompt": "Soll dieser Farb-Namensraum wirklich gelöscht werden? Diese aktion wird neben dem Namensraum ALLE darin enthaltenen Farben löschen!",
|
||||
"color.namespace.delete.title": "Farb-Namensraum löschen",
|
||||
"color.namespace.delete.prompt": "Soll dieser Farbnamespace wirklich gelöscht werden? Diese Aktion wird neben dem Namespace ALLE darin enthaltenen Farben löschen!",
|
||||
"color.namespace.delete.title": "Farbnamespace löschen",
|
||||
"color.new": "Neue Farbe",
|
||||
"color.placeholder": "Farbe",
|
||||
"color.primary": "Primärfarbe",
|
||||
"color.primary_required": "Primärfarbe (erforderlich)",
|
||||
"color.secondary": "Sekundärfarbe",
|
||||
"color.title.no_color": "Keine Farbe",
|
||||
"color_manager.title": "Tag-Farben verwalten",
|
||||
"color_manager.title": "Tagfarben verwalten",
|
||||
"dependency.missing.title": "{dependency} nicht gefunden",
|
||||
"drop_import.description": "Die folgenden Dateien passen zu Dateipfaden, welche bereits in der Bibliothek existieren",
|
||||
"drop_import.duplicates_choice.plural": "Die folgenden {count} Dateien passen zu Dateipfaden, welche bereits in der Bibliothek existieren.",
|
||||
@@ -32,7 +32,7 @@
|
||||
"drop_import.progress.label.singular": "Neue Dateien werden importiert...\n1 Datei importiert.{suffix}",
|
||||
"drop_import.progress.window_title": "Dateien Importieren",
|
||||
"drop_import.title": "Dateikollision(en)",
|
||||
"edit.color_manager": "Tag-Farben verwalten",
|
||||
"edit.color_manager": "Tagfarben verwalten",
|
||||
"edit.copy_fields": "Felder kopieren",
|
||||
"edit.paste_fields": "Felder einfügen",
|
||||
"edit.tag_manager": "Tags Verwalten",
|
||||
@@ -40,29 +40,35 @@
|
||||
"entries.duplicate.merge.label": "Führe doppelte Einträge zusammen…",
|
||||
"entries.duplicate.refresh": "Doppelte Einträge aktualisieren",
|
||||
"entries.duplicates.description": "Doppelte Einträge sind definiert als mehrere Einträge, die auf dieselbe Datei auf der Festplatte verweisen. Durch das Zusammenführen dieser Einträge werden die Tags und Metadaten aller Duplikate zu einem einzigen konsolidierten Eintrag zusammengefasst. Diese sind nicht zu verwechseln mit „doppelten Dateien“, die Duplikate Ihrer Dateien selbst außerhalb von TagStudio sind.",
|
||||
"entries.mirror": "Spiegeln",
|
||||
"entries.mirror.confirmation": "Sind Sie sich sicher, dass Sie die folgenden {count} Einträge spiegeln wollen?",
|
||||
"entries.generic.refresh_alt": "&Aktualisieren",
|
||||
"entries.generic.remove.removing": "Einträge werden gelöscht",
|
||||
"entries.generic.remove.removing_count": "Entferne {count} Einträge...",
|
||||
"entries.ignored.description": "Dateieinträge gelten als \"Ausgeblendet\", wenn sie der Bibliothek hinzugefügt wurden, bevor die Ausblendregelen (in der Datei '.ts_ignore') diese exkludiert hat. Ausgeblendete Dateien bleiben Teil der Bibliothek, damit beim Aktualisieren der Ausblendregeln keine Daten verloren gehen.",
|
||||
"entries.ignored.ignored_count": "Ausgeblendete Einträge: {count}",
|
||||
"entries.ignored.remove": "Entferne ausgeblendete Einträge",
|
||||
"entries.ignored.remove_alt": "Entfer&ne ausgeblendete Einträge",
|
||||
"entries.ignored.scanning": "Dursuche die Bibliothek für ausgeblendete Einträge...",
|
||||
"entries.ignored.title": "Repariere ausgeblendete Einträge",
|
||||
"entries.mirror": "&Spiegeln",
|
||||
"entries.mirror.confirmation": "Sollen folgende {count} Einträge gespiegelt werden?",
|
||||
"entries.mirror.label": "Spiegele {idx}/{total} Einträge...",
|
||||
"entries.mirror.title": "Einträge werden gespiegelt",
|
||||
"entries.mirror.window_title": "Einträge spiegeln",
|
||||
"entries.remove.plural.confirm": "Sollen die folgenden <b>{count}</b> Einträge gelöscht werden? Es werden keine Dateien auf der Festplatte gelöscht.",
|
||||
"entries.remove.singular.confirm": "Soll dieser Eintrag von der Bibliothek entfernt werden? Es werden keine Dateien auf der Festplatte gelöscht.",
|
||||
"entries.running.dialog.new_entries": "Füge {total} neue Dateieinträge hinzu...",
|
||||
"entries.running.dialog.title": "Füge neue Dateieinträge hinzu",
|
||||
"entries.tags": "Tags",
|
||||
"entries.unlinked.delete": "Unverknüpfte Einträge löschen",
|
||||
"entries.unlinked.delete.confirm": "Sind Sie sicher, dass Sie die folgenden {count} Einträge löschen wollen?",
|
||||
"entries.unlinked.delete.deleting": "Einträge werden gelöscht",
|
||||
"entries.unlinked.delete.deleting_count": "Lösche {idx}/{count} unverknüpfte Einträge",
|
||||
"entries.unlinked.delete_alt": "Unverknüpfte Einträge &löschen",
|
||||
"entries.unlinked.description": "Jeder Bibliothekseintrag ist mit einer Datei in einem Ihrer Verzeichnisse verknüpft. Wenn eine Datei, die mit einem Eintrag verknüpft ist, außerhalb von TagStudio verschoben oder gelöscht wird, gilt sie als nicht verknüpft.<br><br>Nicht verknüpfte Einträge können durch das Durchsuchen Ihrer Verzeichnisse automatisch neu verknüpft, vom Benutzer manuell neu verknüpft oder auf Wunsch gelöscht werden.",
|
||||
"entries.unlinked.missing_count.none": "Unverknüpfte Einträge: -",
|
||||
"entries.unlinked.missing_count.some": "Unverknüpfte Einträge: {count}",
|
||||
"entries.unlinked.refresh_all": "Alle aktualisie&ren",
|
||||
"entries.unlinked.relink.attempting": "Versuche {idx}/{missing_count} Einträge wieder zu verknüpfen, {fixed_count} bereits erfolgreich wieder verknüpft",
|
||||
"entries.unlinked.relink.attempting": "Versuche {index}/{unlinked_count} Einträge neu zu verknüpfen, {fixed_count} bereits erfolgreich neu verknüpft",
|
||||
"entries.unlinked.relink.manual": "&Manuell Neuverknüpfen",
|
||||
"entries.unlinked.relink.title": "Einträge werden neuverknüpft",
|
||||
"entries.unlinked.relink.title": "Einträge werden neu verknüpft",
|
||||
"entries.unlinked.remove": "Entferne nicht verknüpfte Einträge",
|
||||
"entries.unlinked.remove_alt": "Entfer&ne nicht verknüpfte Einträge",
|
||||
"entries.unlinked.scanning": "Bibliothek wird nach nicht verknüpften Einträgen durchsucht...",
|
||||
"entries.unlinked.search_and_relink": "&Suchen && Neuverbinden",
|
||||
"entries.unlinked.search_and_relink": "&Suchen && Neuverknüpfen",
|
||||
"entries.unlinked.title": "Unverknüpfte Einträge reparieren",
|
||||
"entries.unlinked.unlinked_count": "Unverknüpfte Einträge: {count}",
|
||||
"ffmpeg.missing.description": "FFmpeg und/oder FFprobe wurden nicht gefunden. FFmpeg ist für multimediale Wiedergabe und Thumbnails vonnöten.",
|
||||
"ffmpeg.missing.status": "{ffmpeg}: {ffmpeg_status}<br>{ffprobe}: {ffprobe_status}",
|
||||
"field.copy": "Feld kopieren",
|
||||
@@ -71,18 +77,18 @@
|
||||
"file.date_added": "Hinzufügungsdatum",
|
||||
"file.date_created": "Erstellungsdatum",
|
||||
"file.date_modified": "Datum geändert",
|
||||
"file.dimensions": "Abmessungen",
|
||||
"file.dimensions": "Dimensionen",
|
||||
"file.duplicates.description": "TagStudio unterstützt das Importieren von DupeGuru-Ergebnissen um Dateiduplikate zu verwalten.",
|
||||
"file.duplicates.dupeguru.advice": "Nach dem Kopiervorgang kann DupeGuru benutzt werden und ungewollte Dateien zu löschen. Anschließend kann TagStudios \"Unverknüpfte Einträge reparieren\" Funktion im \"Werkzeuge\" Menü benutzt werden um die nicht verknüpften Einträge zu löschen.",
|
||||
"file.duplicates.dupeguru.advice": "Nach dem Spiegelvorgang kann DupeGuru benutzt werden und ungewollte Dateien zu löschen. Anschließend kann TagStudios \"Unverknüpfte Einträge reparieren\" Funktion im \"Werkzeuge\" Menü benutzt werden um die nicht verknüpften Einträge zu löschen.",
|
||||
"file.duplicates.dupeguru.file_extension": "DupeGuru-Dateien (*.dupeguru)",
|
||||
"file.duplicates.dupeguru.load_file": "DupeGuru-Datei auswäh&len",
|
||||
"file.duplicates.dupeguru.load_file": "&DupeGuru-Datei laden",
|
||||
"file.duplicates.dupeguru.no_file": "Keine DupeGuru-Datei ausgewählt",
|
||||
"file.duplicates.dupeguru.open_file": "DupeGuru Ergebnisdatei öffnen",
|
||||
"file.duplicates.fix": "Duplizierte Dateien korrigieren",
|
||||
"file.duplicates.fix": "Duplizierte Dateien reparieren",
|
||||
"file.duplicates.matches": "Übereinstimmungen mit duplizierten Dateien: {count}",
|
||||
"file.duplicates.matches_uninitialized": "Übereinstimmungen mit doppelten Dateien: N/A",
|
||||
"file.duplicates.mirror.description": "Kopiert die Eintragsdaten in jeder Duplikatsmenge, wobei alle Daten kombiniert werden ohne Felder zu entfernen oder zu duplizieren. Diese Operation wird keine Dateien oder Daten löschen.",
|
||||
"file.duplicates.mirror_entries": "Einträge kopieren",
|
||||
"file.duplicates.mirror_entries": "&Einträge kopieren",
|
||||
"file.duration": "Länge",
|
||||
"file.not_found": "Datei nicht gefunden",
|
||||
"file.open_file": "Datei öffnen",
|
||||
@@ -115,17 +121,21 @@
|
||||
"generic.missing": "Nicht vorhanden",
|
||||
"generic.navigation.back": "Zurück",
|
||||
"generic.navigation.next": "Weiter",
|
||||
"generic.no": "Nein",
|
||||
"generic.none": "Kein(e)",
|
||||
"generic.overwrite": "Überschreibem",
|
||||
"generic.overwrite_alt": "Überschreiben",
|
||||
"generic.paste": "Einfügen",
|
||||
"generic.recent_libraries": "Aktuelle Bibliotheken",
|
||||
"generic.remove": "Entfernen",
|
||||
"generic.remove_alt": "&Entfernen",
|
||||
"generic.rename": "Umbenennen",
|
||||
"generic.rename_alt": "&Umbenennen",
|
||||
"generic.reset": "Zurücksetzen",
|
||||
"generic.save": "Speichern",
|
||||
"generic.skip": "Überspringen",
|
||||
"generic.skip_alt": "&Überspringen",
|
||||
"generic.yes": "Ja",
|
||||
"home.search": "Suchen",
|
||||
"home.search_entries": "Nach Einträgen suchen",
|
||||
"home.search_library": "Bibliothek durchsuchen",
|
||||
@@ -136,6 +146,7 @@
|
||||
"home.thumbnail_size.medium": "Mittelgroße Vorschau",
|
||||
"home.thumbnail_size.mini": "Mini Vorschau",
|
||||
"home.thumbnail_size.small": "Kleine Vorschau",
|
||||
"ignore.open_file": "Zeige die Datei \"{ts_ignore}\" auf der Festplatte",
|
||||
"json_migration.checking_for_parity": "Parität wird überprüft...",
|
||||
"json_migration.creating_database_tables": "SQL Datenbank Tabellen werden erstellt...",
|
||||
"json_migration.description": "<br>Starte den Migrationsprozess der Bibliothek und sehe die Ergebnisse in der Vorschau an. Die migrierte Bibliothek wird <i>nicht</i> verwendet, bis Sie auf \"Migration abschließen\" klicken.<br><br>Bibliotheksdaten sollten entweder übereinstimmende Werte oder das Label \"Matched\" besitzen. Werte zu denen keine Übereinstimmungen gefunden werden, werden in Rot dargestellt und erhalten das Symbol \"<b>(!)</b>\".<br><center<i>Der Migrationsprozess kann bei größeren Bibliotheken einige Minuten in Anspruch nehmen.</i></center>",
|
||||
@@ -152,7 +163,7 @@
|
||||
"json_migration.heading.parent_tags": "Übergeordnete Tags:",
|
||||
"json_migration.heading.paths": "Pfade:",
|
||||
"json_migration.heading.shorthands": "Kurzformen:",
|
||||
"json_migration.info.description": "Bibliotheksdaten, welche mit TagStudio-Versionen <b>9.4 und niedriger</b> erstellt wurden, müssen in das neue Format <b>v9.5+</b> migriert werden.<br><h2>Was du wissen solltest:</h2><ul><li>Deine bestehenden Bibliotheksdaten werden <b><i>NICHT</i></b> gelöscht.</li><li>Deine persönlichen Dateien werden <b><i>NICHT</i></b> gelöscht, verschoben oder verändert.</li><li>Das neue Format v9.5+ kann nicht von früheren TagStudio-Versionen geöffnet werden.</li></ul>",
|
||||
"json_migration.info.description": "Bibliotheksdaten, welche mit TagStudio-Versionen <b>9.4 und niedriger</b> erstellt wurden, müssen in das neue Format <b>v9.5+</b> migriert werden.<br><h2>Was du wissen solltest:</h2><ul><li>Deine bestehenden Bibliotheksdaten werden <b><i>NICHT</i></b> gelöscht.</li><li>Deine persönlichen Dateien werden <b><i>NICHT</i></b> gelöscht, verschoben oder verändert.</li><li>Das neue Format v9.5+ kann nicht von früheren TagStudio-Versionen geöffnet werden.</li></ul><h3>Änderungen:</h3><ul><li>\"Tagfelder\" wurden mit \"Tagkategorien\" ausgetauscht. Anstatt Tags Feldern hinzuzufügen, werden diese nun direkt den Dateieinträgen hinzugefügt. Diese werden dann, basierend auf den übergeordneten Tags, welche mit der neuen Eigenschaft \"Ist Kategorie\" im Tag Menü markiert wurden, sortiert. Jeder Tag kann als Kategorie deklariert werden und untergeordnete Tags sortieren sich automatisch unter dem übergeordneten Tag, welcher als Kategorie markiert wurde. Der Tag \"Favorit\" und \"Archiviert\" basieren nun standartmäßig auf dem \"Meta Tag\", welcher standartmäßig als Kategorie deklariert ist.</li><li>Tagfarben wurden angepasst und erweitert. Einige Farben wurden umbenannt, oder konsolidiert. Alle bisherigen Tagfarben wandeln sich in die exakten, oder nächstmöglichen Farben in v9.5 um.</li></ul><ul>",
|
||||
"json_migration.migrating_files_entries": "Migriere {entries:,d} Dateieinträge...",
|
||||
"json_migration.migration_complete": "Migration abgeschlossen!",
|
||||
"json_migration.migration_complete_with_discrepancies": "Migration abgeschlossen, Diskrepanzen gefunden",
|
||||
@@ -161,9 +172,6 @@
|
||||
"json_migration.title.new_lib": "<h2>v9.5+ Bibliothek</h2>",
|
||||
"json_migration.title.old_lib": "<h2>v9.4 Bibliothek</h2>",
|
||||
"landing.open_create_library": "Bibliothek öffnen/erstellen {shortcut}",
|
||||
"library_info.stats.entries": "Einträge:",
|
||||
"library_info.stats.fields": "Felder:",
|
||||
"library_info.stats.tags": "Tags:",
|
||||
"library.field.add": "Feld hinzufügen",
|
||||
"library.field.confirm_remove": "Wollen Sie dieses \"{name}\" Feld wirklich entfernen?",
|
||||
"library.field.mixed_data": "Gemischte Daten",
|
||||
@@ -172,12 +180,27 @@
|
||||
"library.name": "Bibliothek",
|
||||
"library.refresh.scanning.plural": "Durchsuche Verzeichnisse nach neuen Dateien...\n{searched_count} Dateien durchsucht, {found_count} neue Dateien gefunden",
|
||||
"library.refresh.scanning.singular": "Durchsuche Verzeichnisse nach neuen Dateien...\n{searched_count} Datei durchsucht, {found_count} neue Datei gefunden",
|
||||
"library_info.cleanup.backups": "Bibliotheks-Backups:",
|
||||
"library_info.cleanup.dupe_files": "Doppelte Dateien:",
|
||||
"library_info.cleanup.ignored": "Ausgeblendete Einträge:",
|
||||
"library_info.cleanup.legacy_json": "Übriggebliebene Legacybibliotheken:",
|
||||
"library_info.cleanup.unlinked": "Nicht verlinkte Einträge:",
|
||||
"library_info.cleanup": "Aufräumen",
|
||||
"library_info.stats.colors": "Tagfarben:",
|
||||
"library_info.stats.entries": "Einträge:",
|
||||
"library_info.stats.fields": "Felder:",
|
||||
"library_info.stats.macros": "Macros:",
|
||||
"library_info.stats.namespaces": "Namespaces:",
|
||||
"library_info.stats.tags": "Tags:",
|
||||
"library_info.stats": "Statistiken",
|
||||
"library_info.title": "Bibliothek '{library_dir}'",
|
||||
"library_info.version": "Formatsversion der Bibliothek: {version}",
|
||||
"library_object.name_required": "Name (erforderlich)",
|
||||
"library_object.name": "Name",
|
||||
"library_object.slug": "ID Schlüssel",
|
||||
"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",
|
||||
"library_object.name": "Name",
|
||||
"library_object.name_required": "Name (erforderlich)",
|
||||
"library_object.slug": "ID Schlüssel",
|
||||
"library_object.slug_required": "ID Schlüssel (erforderlich)",
|
||||
"macros.running.dialog.new_entries": "Führe konfigurierte Makros für {count}/{total} neue Dateieinträge aus...",
|
||||
"macros.running.dialog.title": "Ausführen von Makros bei neuen Einträgen",
|
||||
@@ -196,6 +219,7 @@
|
||||
"menu.file.missing_library.message": "Der Pfad für die Bibliothek \"{library}\" kann nicht gefunden werden.",
|
||||
"menu.file.missing_library.title": "Nicht vorhandene Bibliothek",
|
||||
"menu.file.new_library": "Neue Bibliothek",
|
||||
"menu.file.open_backups_folder": "Bibliotheksbackupordner öffnen",
|
||||
"menu.file.open_create_library": "Bibli&othek öffnen/erstellen",
|
||||
"menu.file.open_library": "Bibliothek öffnen",
|
||||
"menu.file.open_recent_library": "Zuletzt verwendete öffnen",
|
||||
@@ -210,16 +234,22 @@
|
||||
"menu.settings": "Optionen...",
|
||||
"menu.tools": "Werkzeuge",
|
||||
"menu.tools.fix_duplicate_files": "Duplizierte &Dateien reparieren",
|
||||
"menu.tools.fix_ignored_entries": "&Ausgeblendete Einträge reparieren",
|
||||
"menu.tools.fix_unlinked_entries": "&Unverknüpfte Einträge reparieren",
|
||||
"menu.view": "Ansicht",
|
||||
"menu.view.decrease_thumbnail_size": "Thumbnailgröße verkleinern",
|
||||
"menu.view.increase_thumbnail_size": "Thumbnailgröße vergrößern",
|
||||
"menu.view.library_info": "Bibliotheks&informationen",
|
||||
"menu.window": "Fenster",
|
||||
"namespace.create.description": "Namespaces werden von Tagstudio verwendet, um Gruppen von Objekten (bspw. Tags oder Farben) so darzustellen, dass sie einfach exportiert und geteilt werden können. Namespaces, die mit \"tagstudio\" beginnen sind für interne Vorgänge von Tagstudio reserviert.",
|
||||
"namespace.create.description": "Namespaces werden von TagStudio verwendet, um Gruppen von Objekten (bspw. Tags oder Farben) so darzustellen, dass sie einfach exportiert und geteilt werden können. Namespaces, die mit \"tagstudio\" beginnen sind für interne Vorgänge von Tagstudio reserviert.",
|
||||
"namespace.create.description_color": "Tagfarben nutzen Namespaces als Farbpalettengruppen. Alle benutzerdefinierten Farben müssen erst einer Namespacegruppe zugeordnet werden.",
|
||||
"namespace.create.title": "Namensraum erstellen",
|
||||
"namespace.new.button": "Neuer Namensraum",
|
||||
"namespace.new.prompt": "Erstelle einen neuen Namensraum um eigene Farben hinzuzufügen!",
|
||||
"preview.ignored": "Ausgeblendet",
|
||||
"preview.multiple_selection": "<b>{count}</b> Elemente ausgewählt",
|
||||
"preview.no_selection": "Keine Elemente ausgewählt",
|
||||
"preview.unlinked": "Nicht verlinkt",
|
||||
"select.add_tag_to_selected": "Tag zu Ausgewähltem hinzufügen",
|
||||
"select.all": "Alle auswählen",
|
||||
"select.clear": "Auswahl leeren",
|
||||
@@ -233,6 +263,7 @@
|
||||
"settings.filepath.option.full": "Zeige vollständigen Pfad",
|
||||
"settings.filepath.option.name": "Nur Dateinamen anzeigen",
|
||||
"settings.filepath.option.relative": "Zeige relative Pfade",
|
||||
"settings.generate_thumbs": "Generiere Thumbnails",
|
||||
"settings.global": "Globale Einstellungen",
|
||||
"settings.hourformat.label": "24-Stunden Format",
|
||||
"settings.language": "Sprache",
|
||||
@@ -254,6 +285,7 @@
|
||||
"settings.zeropadding.label": "Platzsparendes Datum",
|
||||
"sorting.direction.ascending": "Aufsteigend",
|
||||
"sorting.direction.descending": "Absteigend",
|
||||
"sorting.mode.random": "Zufällig",
|
||||
"splash.opening_library": "Öffne Bibliothek \"{library_path}\"...",
|
||||
"status.deleted_file_plural": "{count} Dateien gelöscht!",
|
||||
"status.deleted_file_singular": "1 Datei gelöscht!",
|
||||
|
||||
@@ -40,29 +40,35 @@
|
||||
"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.generic.refresh_alt": "&Refresh",
|
||||
"entries.generic.remove.removing_count": "Removing {count} Entries...",
|
||||
"entries.generic.remove.removing": "Removing Entries",
|
||||
"entries.ignored.description": "File entries are considered to be \"ignored\" if they were added to the library before the user's ignore rules (via the '.ts_ignore' file) were updated to exclude it. Ignored files are kept in the library by default in order to prevent accidental data loss when updating ignore rules.",
|
||||
"entries.ignored.ignored_count": "Ignored Entries: {count}",
|
||||
"entries.ignored.remove_alt": "Remo&ve Ignored Entries",
|
||||
"entries.ignored.remove": "Remove Ignored Entries",
|
||||
"entries.ignored.scanning": "Scanning Library for Ignored Entries...",
|
||||
"entries.ignored.title": "Fix Ignored 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.window_title": "Mirror Entries",
|
||||
"entries.mirror": "&Mirror",
|
||||
"entries.remove.plural.confirm": "Are you sure you want to remove these <b>{count}</b> entries from your library? No files on disk will be deleted.",
|
||||
"entries.remove.singular.confirm": "Are you sure you want to remove this entry from your library? No files on disk will be deleted.",
|
||||
"entries.running.dialog.new_entries": "Adding {total} New File Entries...",
|
||||
"entries.running.dialog.title": "Adding New File Entries",
|
||||
"entries.tags": "Tags",
|
||||
"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.<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.attempting": "Attempting to Relink {index}/{unlinked_count} Entries, {fixed_count} Successfully Relinked",
|
||||
"entries.unlinked.relink.manual": "&Manual Relink",
|
||||
"entries.unlinked.relink.title": "Relinking Entries",
|
||||
"entries.unlinked.remove_alt": "Remo&ve Unlinked Entries",
|
||||
"entries.unlinked.remove": "Remove Unlinked Entries",
|
||||
"entries.unlinked.scanning": "Scanning Library for Unlinked Entries...",
|
||||
"entries.unlinked.search_and_relink": "&Search && Relink",
|
||||
"entries.unlinked.title": "Fix Unlinked Entries",
|
||||
"entries.unlinked.unlinked_count": "Unlinked Entries: {count}",
|
||||
"ffmpeg.missing.description": "FFmpeg and/or FFprobe were not found. FFmpeg is required for multimedia playback and thumbnails.",
|
||||
"ffmpeg.missing.status": "{ffmpeg}: {ffmpeg_status}<br>{ffprobe}: {ffprobe_status}",
|
||||
"field.copy": "Copy Field",
|
||||
@@ -71,7 +77,6 @@
|
||||
"file.date_added": "Date Added",
|
||||
"file.date_created": "Date Created",
|
||||
"file.date_modified": "Date Modified",
|
||||
"file.path": "File Path",
|
||||
"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.",
|
||||
@@ -91,6 +96,7 @@
|
||||
"file.open_location.generic": "Show file in file explorer",
|
||||
"file.open_location.mac": "Reveal in Finder",
|
||||
"file.open_location.windows": "Show in File Explorer",
|
||||
"file.path": "File Path",
|
||||
"folders_to_tags.close_all": "Close All",
|
||||
"folders_to_tags.converting": "Converting folders to Tags",
|
||||
"folders_to_tags.description": "Creates tags based on your folder structure and applies them to your entries.\n The structure below shows all the tags that will be created and what entries they will be applied to.",
|
||||
@@ -115,17 +121,21 @@
|
||||
"generic.missing": "Missing",
|
||||
"generic.navigation.back": "Back",
|
||||
"generic.navigation.next": "Next",
|
||||
"generic.no": "No",
|
||||
"generic.none": "None",
|
||||
"generic.overwrite_alt": "&Overwrite",
|
||||
"generic.overwrite": "Overwrite",
|
||||
"generic.paste": "Paste",
|
||||
"generic.recent_libraries": "Recent Libraries",
|
||||
"generic.remove_alt": "&Remove",
|
||||
"generic.remove": "Remove",
|
||||
"generic.rename_alt": "&Rename",
|
||||
"generic.rename": "Rename",
|
||||
"generic.reset": "Reset",
|
||||
"generic.save": "Save",
|
||||
"generic.skip_alt": "&Skip",
|
||||
"generic.skip": "Skip",
|
||||
"generic.yes": "Yes",
|
||||
"home.search_entries": "Search Entries",
|
||||
"home.search_library": "Search Library",
|
||||
"home.search_tags": "Search Tags",
|
||||
@@ -162,6 +172,12 @@
|
||||
"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_info.cleanup.backups": "Library Backups:",
|
||||
"library_info.cleanup.dupe_files": "Duplicate Files:",
|
||||
"library_info.cleanup.ignored": "Ignored Entries:",
|
||||
"library_info.cleanup.legacy_json": "Leftover Legacy Library:",
|
||||
"library_info.cleanup.unlinked": "Unlinked Entries:",
|
||||
"library_info.cleanup": "Cleanup",
|
||||
"library_info.stats.colors": "Tag Colors:",
|
||||
"library_info.stats.entries": "Entries:",
|
||||
"library_info.stats.fields": "Fields:",
|
||||
@@ -170,6 +186,7 @@
|
||||
"library_info.stats.tags": "Tags:",
|
||||
"library_info.stats": "Statistics",
|
||||
"library_info.title": "Library '{library_dir}'",
|
||||
"library_info.version": "Library Format Version: {version}",
|
||||
"library_object.name_required": "Name (Required)",
|
||||
"library_object.name": "Name",
|
||||
"library_object.slug_required": "ID Slug (Required)",
|
||||
@@ -201,6 +218,7 @@
|
||||
"menu.file.missing_library.message": "The location of the library \"{library}\" cannot be found.",
|
||||
"menu.file.missing_library.title": "Missing Library",
|
||||
"menu.file.new_library": "New Library",
|
||||
"menu.file.open_backups_folder": "Open Backups Folder",
|
||||
"menu.file.open_create_library": "&Open/Create Library",
|
||||
"menu.file.open_library": "Open Library",
|
||||
"menu.file.open_recent_library": "Open Recent",
|
||||
@@ -214,7 +232,8 @@
|
||||
"menu.macros": "&Macros",
|
||||
"menu.select": "Select",
|
||||
"menu.settings": "Settings...",
|
||||
"menu.tools.fix_duplicate_files": "Fix Duplicate &Files",
|
||||
"menu.tools.fix_duplicate_files": "Fix &Duplicate Files",
|
||||
"menu.tools.fix_ignored_entries": "Fix &Ignored Entries",
|
||||
"menu.tools.fix_unlinked_entries": "Fix &Unlinked Entries",
|
||||
"menu.tools": "&Tools",
|
||||
"menu.view.decrease_thumbnail_size": "Decrease Thumbnail Size",
|
||||
@@ -236,34 +255,40 @@
|
||||
"select.clear": "Clear Selection",
|
||||
"select.inverse": "Invert Selection",
|
||||
"settings.clear_thumb_cache.title": "Clear Thumbnail Cache",
|
||||
"settings.dateformat.english": "English",
|
||||
"settings.dateformat.international": "International",
|
||||
"settings.dateformat.label": "Date Format",
|
||||
"settings.dateformat.system": "System",
|
||||
"settings.filepath.label": "Filepath Visibility",
|
||||
"settings.filepath.option.full": "Show Full Paths",
|
||||
"settings.filepath.option.name": "Show Filenames Only",
|
||||
"settings.filepath.option.relative": "Show Relative Paths",
|
||||
"settings.generate_thumbs": "Thumbnail Generation",
|
||||
"settings.global": "Global Settings",
|
||||
"settings.hourformat.label": "24-Hour Time",
|
||||
"settings.language": "Language",
|
||||
"settings.library": "Library Settings",
|
||||
"settings.open_library_on_start": "Open Library on Start",
|
||||
"settings.generate_thumbs": "Thumbnail Generation",
|
||||
"settings.page_size": "Page Size",
|
||||
"settings.restart_required": "Please restart TagStudio for changes to take effect.",
|
||||
"settings.show_filenames_in_grid": "Show Filenames in Grid",
|
||||
"settings.show_recent_libraries": "Show Recent Libraries",
|
||||
"settings.tag_click_action.label": "Tag Click Action",
|
||||
"settings.splash.label": "Splash Screen",
|
||||
"settings.splash.option.classic": "Classic (9.0)",
|
||||
"settings.splash.option.default": "Default",
|
||||
"settings.splash.option.goo_gears": "Open Source (9.4)",
|
||||
"settings.splash.option.ninety_five": "'95 (9.5)",
|
||||
"settings.splash.option.random": "Random",
|
||||
"settings.tag_click_action.add_to_search": "Add Tag to Search",
|
||||
"settings.tag_click_action.label": "Tag Click Action",
|
||||
"settings.tag_click_action.open_edit": "Edit Tag",
|
||||
"settings.tag_click_action.set_search": "Search for Tag",
|
||||
"settings.theme.dark": "Dark",
|
||||
"settings.theme.label": "Theme:",
|
||||
"settings.theme.light": "Light",
|
||||
"settings.theme.system": "System",
|
||||
"settings.dateformat.label": "Date Format",
|
||||
"settings.dateformat.system": "System",
|
||||
"settings.dateformat.english": "English",
|
||||
"settings.dateformat.international": "International",
|
||||
"settings.hourformat.label": "24-Hour Time",
|
||||
"settings.zeropadding.label": "Date Zero-Padding",
|
||||
"settings.title": "Settings",
|
||||
"settings.zeropadding.label": "Date Zero-Padding",
|
||||
"sorting.direction.ascending": "Ascending",
|
||||
"sorting.direction.descending": "Descending",
|
||||
"sorting.mode.random": "Random",
|
||||
|
||||
@@ -40,29 +40,24 @@
|
||||
"entries.duplicate.merge.label": "Fusionando entradas duplicadas...",
|
||||
"entries.duplicate.refresh": "Recargar entradas duplicadas",
|
||||
"entries.duplicates.description": "Las entradas duplicadas se definen como múltiples entradas que apuntan al mismo archivo en el disco. Al fusionarlas, se combinarán las etiquetas y los metadatos de todos los duplicados en una única entrada consolidada. No deben confundirse con los \"archivos duplicados\", que son duplicados de sus archivos fuera de TagStudio.",
|
||||
"entries.generic.remove.removing": "Eliminando entradas",
|
||||
"entries.mirror": "&Reflejar",
|
||||
"entries.mirror.confirmation": "¿Estás seguro de que quieres reflejar las siguientes {count} entradas?",
|
||||
"entries.mirror.label": "Reflejando {idx}/{total} Entradas...",
|
||||
"entries.mirror.title": "Reflejando entradas",
|
||||
"entries.mirror.window_title": "Reflejar entradas",
|
||||
"entries.remove.plural.confirm": "¿Está seguro de que desea eliminar las siguientes {count} entradas?",
|
||||
"entries.running.dialog.new_entries": "Añadiendo {total} nuevas entradas de archivos...",
|
||||
"entries.running.dialog.title": "Añadiendo las nuevas entradas de archivos",
|
||||
"entries.tags": "Etiquetas",
|
||||
"entries.unlinked.delete": "Eliminar entradas no vinculadas",
|
||||
"entries.unlinked.delete.confirm": "¿Está seguro de que desea eliminar las siguientes {count} entradas?",
|
||||
"entries.unlinked.delete.deleting": "Eliminando entradas",
|
||||
"entries.unlinked.delete.deleting_count": "Eliminando {idx}/{count} entradas no vinculadas",
|
||||
"entries.unlinked.delete_alt": "Eliminar entradas no vinculadas",
|
||||
"entries.unlinked.description": "Cada entrada de la biblioteca está vinculada a un archivo en uno de tus directorios. Si un archivo vinculado a una entrada se mueve o se elimina fuera de TagStudio, se considerará desvinculado. <br><br>Las entradas no vinculadas se pueden volver a vincular automáticamente mediante una búsqueda en tus directorios, el usuario puede eliminarlas si así lo desea.",
|
||||
"entries.unlinked.missing_count.none": "Entradas no vinculadas: N/A",
|
||||
"entries.unlinked.missing_count.some": "Entradas no vinculadas: {count}",
|
||||
"entries.unlinked.refresh_all": "&Recargar todo",
|
||||
"entries.unlinked.relink.attempting": "Intentando volver a vincular {idx}/{missing_count} Entradas, {fixed_count} Reenlazado correctamente",
|
||||
"entries.unlinked.relink.attempting": "Intentando volver a vincular {index}/{unlinked_count} Entradas, {fixed_count} Reenlazado correctamente",
|
||||
"entries.unlinked.relink.manual": "&Reenlace manual",
|
||||
"entries.unlinked.relink.title": "Volver a vincular las entradas",
|
||||
"entries.unlinked.scanning": "Buscando entradas no enlazadas en la biblioteca...",
|
||||
"entries.unlinked.search_and_relink": "&Buscar && volver a vincular",
|
||||
"entries.unlinked.title": "Corregir entradas no vinculadas",
|
||||
"entries.unlinked.unlinked_count": "Entradas no vinculadas: {count}",
|
||||
"ffmpeg.missing.description": "No se ha encontrado FFmpeg y/o FFprobe. Se requiere de FFmpeg para la reproducción de contenido multimedia y las miniaturas.",
|
||||
"ffmpeg.missing.status": "{ffmpeg}: {ffmpeg_status}<br>{ffprobe}: {ffprobe_status}",
|
||||
"field.copy": "Copiar campo",
|
||||
@@ -161,9 +156,6 @@
|
||||
"json_migration.title.new_lib": "<h2>v9.5+ biblioteca</h2>",
|
||||
"json_migration.title.old_lib": "<h2>v9.4 biblioteca</h2>",
|
||||
"landing.open_create_library": "Abrir/Crear biblioteca {shortcut}",
|
||||
"library_info.stats.entries": "Entradas:",
|
||||
"library_info.stats.fields": "Campos:",
|
||||
"library_info.stats.tags": "Etiquetas:",
|
||||
"library.field.add": "Añadir campo",
|
||||
"library.field.confirm_remove": "¿Está seguro de que desea eliminar el campo \"{name}\"?",
|
||||
"library.field.mixed_data": "Datos variados",
|
||||
@@ -175,6 +167,9 @@
|
||||
"library.refresh.scanning_preparing": "Buscar archivos nuevos en los directorios...\nPreparando...",
|
||||
"library.refresh.title": "Refrescar directorios",
|
||||
"library.scan_library.title": "Escaneando la biblioteca",
|
||||
"library_info.stats.entries": "Entradas:",
|
||||
"library_info.stats.fields": "Campos:",
|
||||
"library_info.stats.tags": "Etiquetas:",
|
||||
"library_object.name": "Nombre",
|
||||
"library_object.name_required": "Nombre (Obligatorio)",
|
||||
"library_object.slug": "Slug ID",
|
||||
|
||||
@@ -40,29 +40,24 @@
|
||||
"entries.duplicate.merge.label": "Sinasama ang mga Duplicate na Entry…",
|
||||
"entries.duplicate.refresh": "I-refresh ang Mga Duplicate na Entry",
|
||||
"entries.duplicates.description": "Ang mga duplicate na entry ay tinukoy bilang maramihang mga entry na tumuturo sa parehong file sa disk. Ang pagsasama-sama ng mga ito ay pagsasama-samahin ang mga tag at metadata mula sa lahat ng mga duplicate sa isang solong pinagsama-samang entry. Ang mga ito ay hindi dapat ipagkamali sa \"mga duplicate na file\", na mga duplicate ng iyong mga file mismo sa labas ng TagStudio.",
|
||||
"entries.generic.remove.removing": "Binubura ang Mga Entry",
|
||||
"entries.mirror": "I-&Mirror",
|
||||
"entries.mirror.confirmation": "Sigurado ka ba gusto mong i-mirror ang sumusunod na {count} Mga Entry?",
|
||||
"entries.mirror.label": "Mini-mirror ang {idx}/{total} Mga Entry…",
|
||||
"entries.mirror.title": "Mini-mirror ang Mga Entry",
|
||||
"entries.mirror.window_title": "I-mirror ang Mga Entry",
|
||||
"entries.remove.plural.confirm": "Sigurado ka ba gusto mong burahin ang (mga) sumusunod na {count} entry?",
|
||||
"entries.running.dialog.new_entries": "Dinadagdag ang {total} Mga Bagong Entry ng File…",
|
||||
"entries.running.dialog.title": "Dinadagdag ang Mga Bagong Entry ng File",
|
||||
"entries.tags": "Mga Tag",
|
||||
"entries.unlinked.delete": "Burahin ang Mga Hindi Naka-link na Entry",
|
||||
"entries.unlinked.delete.confirm": "Sigurado ka ba gusto mong burahin ang (mga) sumusunod na {count} entry?",
|
||||
"entries.unlinked.delete.deleting": "Binubura ang Mga Entry",
|
||||
"entries.unlinked.delete.deleting_count": "Binubura ang {idx}/{count} (mga) Naka-unlink na Entry",
|
||||
"entries.unlinked.delete_alt": "Burahin ang Mga Naka-unlink na Entry",
|
||||
"entries.unlinked.description": "Ang bawat entry sa library ay naka-link sa isang file sa isa sa iyong mga direktoryo. Kung ang isang file na naka-link sa isang entry ay inilipat o binura sa labas ng TagStudio, ito ay isinasaalang-alang na naka-unlink.<br><br>Ang mga naka-unlink na entry ay maaring i-link muli sa pamamagitan ng paghahanap sa iyong mga direktoryo o buburahin kung ninanais.",
|
||||
"entries.unlinked.missing_count.none": "Mga Naka-unlink na Entry: N/A",
|
||||
"entries.unlinked.missing_count.some": "Mga Naka-unlink na Entry: {count}",
|
||||
"entries.unlinked.refresh_all": "&I-refresh Lahat",
|
||||
"entries.unlinked.relink.attempting": "Sinusubukang i-link muli ang {idx}/{missing_count} Mga Entry, {fixed_count} Matagumpay na na-link muli",
|
||||
"entries.unlinked.relink.attempting": "Sinusubukang i-link muli ang {index}/{unlinked_count} Mga Entry, {fixed_count} Matagumpay na na-link muli",
|
||||
"entries.unlinked.relink.manual": "&Manwal na Pag-link Muli",
|
||||
"entries.unlinked.relink.title": "Nili-link muli ang Mga Entry",
|
||||
"entries.unlinked.scanning": "Sina-scan ang Library para sa Mga Naka-unlink na Entry…",
|
||||
"entries.unlinked.search_and_relink": "&Maghanap at Mag-link muli",
|
||||
"entries.unlinked.title": "Ayusin ang Mga Naka-unlink na Entry",
|
||||
"entries.unlinked.unlinked_count": "Mga Naka-unlink na Entry: {count}",
|
||||
"ffmpeg.missing.description": "Hindi nahanap ang FFmpeg at/o FFprobe. Kinakailangan ang FFmpeg para sa playback ng multimedia at mga thumbnail.",
|
||||
"ffmpeg.missing.status": "{ffmpeg}: {ffmpeg_status}<br>{ffprobe}: {ffprobe_status}",
|
||||
"field.copy": "Kopyahin ang Field",
|
||||
@@ -161,9 +156,6 @@
|
||||
"json_migration.title.new_lib": "<h2>v9.5+ na Library</h2>",
|
||||
"json_migration.title.old_lib": "<h2>v9.4 na Library</h2>",
|
||||
"landing.open_create_library": "Buksan/Gumawa ng Library {shortcut}",
|
||||
"library_info.stats.entries": "Mga entry:",
|
||||
"library_info.stats.fields": "Mga Field:",
|
||||
"library_info.stats.tags": "Mga tag:",
|
||||
"library.field.add": "Magdagdag ng Field",
|
||||
"library.field.confirm_remove": "Sigurado ka ba gusto mo tanggalin ang field na \"{name}\"?",
|
||||
"library.field.mixed_data": "Halo-halong Data",
|
||||
@@ -175,6 +167,9 @@
|
||||
"library.refresh.scanning_preparing": "Sina-scan ang Mga Direktoryo para sa Mga Bagong File...\nNaghahanda...",
|
||||
"library.refresh.title": "Nire-refresh ang Mga Direktoryo",
|
||||
"library.scan_library.title": "Sina-scan ang Library",
|
||||
"library_info.stats.entries": "Mga entry:",
|
||||
"library_info.stats.fields": "Mga Field:",
|
||||
"library_info.stats.tags": "Mga tag:",
|
||||
"library_object.name": "Pangalan",
|
||||
"library_object.name_required": "Pangalan (Kinakailangan)",
|
||||
"library_object.slug": "Slug ng ID",
|
||||
|
||||
@@ -40,29 +40,35 @@
|
||||
"entries.duplicate.merge.label": "Fusionner les entrées dupliquées...",
|
||||
"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 tags 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.",
|
||||
"entries.generic.refresh_alt": "&Recharger",
|
||||
"entries.generic.remove.removing": "Suppression des Entrées",
|
||||
"entries.generic.remove.removing_count": "Suppressions de {count} entrées...",
|
||||
"entries.ignored.description": "Les entrées de fichier sont considérées comme « ignorées » si elles ont été ajoutées à la bibliothèque avant que les règles d'ignorance de l'utilisateur (via le fichier « .ts_ignore ») aient été mises à jour pour les exclure. Les fichiers ignorés sont conservés dans la bibliothèque par défaut afin d'éviter toute perte accidentelle de données lors de la mise à jour des règles d'ignorance.",
|
||||
"entries.ignored.ignored_count": "Entrées Ignorées : {count}",
|
||||
"entries.ignored.remove": "Supprimer les entrées ignorées",
|
||||
"entries.ignored.remove_alt": "Supprim&er les entrées ignorées",
|
||||
"entries.ignored.scanning": "Recherche des entrées ignorées dans la bibliothèque...",
|
||||
"entries.ignored.title": "Corriger les entrées ignorées",
|
||||
"entries.mirror": "&Refléter",
|
||||
"entries.mirror.confirmation": "Êtes-vous sûr de vouloir répliquer les {count} Entrées suivantes ?",
|
||||
"entries.mirror.label": "Réplication de {idx}/{total} Entrées...",
|
||||
"entries.mirror.title": "Réplication des Entrées",
|
||||
"entries.mirror.window_title": "Entrée Miroir",
|
||||
"entries.remove.plural.confirm": "Êtes-vous sûr de vouloir supprimer les <b>{count}</b> entrées suivantes ? Aucun fichiers sur votre disque ne sera supprimée.",
|
||||
"entries.remove.singular.confirm": "Êtes-vous sûr de vouloir supprimer cette entrée de votre bibliothèque ? Aucun fichier sur le disque ne sera supprimé.",
|
||||
"entries.running.dialog.new_entries": "Ajout de {total} Nouvelles entrées de fichier...",
|
||||
"entries.running.dialog.title": "Ajout de Nouvelles entrées de fichier",
|
||||
"entries.tags": "Tags",
|
||||
"entries.unlinked.delete": "Supprimer les Entrées non Liées",
|
||||
"entries.unlinked.delete.confirm": "Êtes-vous sûr de vouloir supprimer les {count} entrées suivantes ?",
|
||||
"entries.unlinked.delete.deleting": "Suppression des Entrées",
|
||||
"entries.unlinked.delete.deleting_count": "Suppression des Entrées non Liées {idx}/{count}",
|
||||
"entries.unlinked.delete_alt": "Supprimer les Entrées non liées",
|
||||
"entries.unlinked.description": "Chaque entrée dans la bibliothèque est liée à un fichier dans l'un de vos dossiers. Si un fichier lié à une entrée est déplacé ou supprimé en dehors de TagStudio, il est alors considéré non lié. <br><br>Les entrées non liées peuvent être automatiquement reliées via la recherche dans vos dossiers, reliées manuellement par l'utilisateur, ou supprimées si désiré.",
|
||||
"entries.unlinked.missing_count.none": "Entrées non Liées : N/A",
|
||||
"entries.unlinked.missing_count.some": "Entrées non Liées : {count}",
|
||||
"entries.unlinked.refresh_all": "&Tout Rafraîchir",
|
||||
"entries.unlinked.relink.attempting": "Tentative de Reliage de {idx}/{missing_count} Entrées, {fixed_count} ont été Reliées avec Succès",
|
||||
"entries.unlinked.relink.attempting": "Tentative de Reliage de {index}/{unlinked_count} Entrées, {fixed_count} ont été Reliées avec Succès",
|
||||
"entries.unlinked.relink.manual": "&Reliage Manuel",
|
||||
"entries.unlinked.relink.title": "Reliage des Entrées",
|
||||
"entries.unlinked.remove": "Supprimer les entrées non liées",
|
||||
"entries.unlinked.remove_alt": "Supprim&er les entrées non liées",
|
||||
"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",
|
||||
"entries.unlinked.unlinked_count": "Entrées non Liées : {count}",
|
||||
"ffmpeg.missing.description": "FFmpeg et/ou FFprobe n’ont pas été trouvée. FFmpeg est nécessaire pour la lecture de média et les vignettes.",
|
||||
"ffmpeg.missing.status": "{ffmpeg} : {ffmpeg_status}<br>{ffprobe} : {ffprobe_status}",
|
||||
"field.copy": "Copier le Champ",
|
||||
@@ -115,17 +121,21 @@
|
||||
"generic.missing": "Manquant",
|
||||
"generic.navigation.back": "Retour",
|
||||
"generic.navigation.next": "Suivant",
|
||||
"generic.no": "Non",
|
||||
"generic.none": "Aucun",
|
||||
"generic.overwrite": "Écraser",
|
||||
"generic.overwrite_alt": "&Écraser",
|
||||
"generic.paste": "Coller",
|
||||
"generic.recent_libraries": "Bibliothèques Récentes",
|
||||
"generic.remove": "Supprimer",
|
||||
"generic.remove_alt": "&Supprimer",
|
||||
"generic.rename": "Renommer",
|
||||
"generic.rename_alt": "&Renommer",
|
||||
"generic.reset": "Réinitialiser",
|
||||
"generic.save": "Sauvegarder",
|
||||
"generic.skip": "Passer",
|
||||
"generic.skip_alt": "&Passer",
|
||||
"generic.yes": "Oui",
|
||||
"home.search": "Rechercher",
|
||||
"home.search_entries": "Recherche",
|
||||
"home.search_library": "Rechercher dans la Bibliothèque",
|
||||
@@ -136,6 +146,7 @@
|
||||
"home.thumbnail_size.medium": "Miniatures Moyennes",
|
||||
"home.thumbnail_size.mini": "Mini Miniatures",
|
||||
"home.thumbnail_size.small": "Petites Miniatures",
|
||||
"ignore.open_file": "Afficher le fichier \"{ts_ignore}\" sur le Disque",
|
||||
"json_migration.checking_for_parity": "Vérification de la Parité...",
|
||||
"json_migration.creating_database_tables": "Création des Tables de Base de Données SQL...",
|
||||
"json_migration.description": "<br>Démarrez et prévisualisez les résultats du processus de migration de la bibliothèque. La bibliothèque convertie <i>ne</i> sera utilisée que si vous cliquez sur \"Terminer la migration\". <br><br>Les données de la bibliothèque doivent soit avoir des valeurs correspondantes, soit comporter un label \"Matched\". Les valeurs qui ne correspondent pas seront affichées en rouge et comporteront un symbole \"<b>(!)</b>\" à côté d'elles.<br><center><i>Ce processus peut prendre jusqu'à plusieurs minutes pour les bibliothèques plus volumineuses.</i></center>",
|
||||
@@ -161,9 +172,6 @@
|
||||
"json_migration.title.new_lib": "<h2>Bibliothèque v9.5+</h2>",
|
||||
"json_migration.title.old_lib": "<h2>Bibliothèque v9.4</h2>",
|
||||
"landing.open_create_library": "Ouvrir/Créer une Bibliothèque {shortcut}",
|
||||
"library_info.stats.entries": "Entrées :",
|
||||
"library_info.stats.fields": "Champs :",
|
||||
"library_info.stats.tags": "Tags :",
|
||||
"library.field.add": "Ajouter un Champ",
|
||||
"library.field.confirm_remove": "Êtes-vous sûr de vouloir supprimer le champ \"{name}\"?",
|
||||
"library.field.mixed_data": "Données Mélangées",
|
||||
@@ -175,6 +183,21 @@
|
||||
"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",
|
||||
"library_info.cleanup": "Nettoyage",
|
||||
"library_info.cleanup.backups": "Sauvegardes de bibliothèque :",
|
||||
"library_info.cleanup.dupe_files": "Fichiers en double :",
|
||||
"library_info.cleanup.ignored": "Entrées ignorées :",
|
||||
"library_info.cleanup.legacy_json": "Vestiges de la Bibliothèque :",
|
||||
"library_info.cleanup.unlinked": "Entrées non liées :",
|
||||
"library_info.stats": "Statistiques",
|
||||
"library_info.stats.colors": "Couleurs de Tag :",
|
||||
"library_info.stats.entries": "Entrées :",
|
||||
"library_info.stats.fields": "Champs :",
|
||||
"library_info.stats.macros": "Macros :",
|
||||
"library_info.stats.namespaces": "Namespaces :",
|
||||
"library_info.stats.tags": "Tags :",
|
||||
"library_info.title": "Bibliothèque '{library_dir}'",
|
||||
"library_info.version": "Version du format de bibliothèque : {version}",
|
||||
"library_object.name": "Nom",
|
||||
"library_object.name_required": "Nom (Requis)",
|
||||
"library_object.slug": "Identifiant unique",
|
||||
@@ -196,6 +219,7 @@
|
||||
"menu.file.missing_library.message": "L'emplacement de la bibliothèque \"{library}\" n'a pas été trouvée.",
|
||||
"menu.file.missing_library.title": "Bibliothèque Manquante",
|
||||
"menu.file.new_library": "Nouvelle Bibliothèque",
|
||||
"menu.file.open_backups_folder": "Ouvrir le dossier des sauvegardes",
|
||||
"menu.file.open_create_library": "&Ouvrir/Créer une Bibliothèque",
|
||||
"menu.file.open_library": "Ouvrir la Bibliothèque",
|
||||
"menu.file.open_recent_library": "Ouvrir la Bibliothèque récente",
|
||||
@@ -209,11 +233,13 @@
|
||||
"menu.select": "Sélectionner",
|
||||
"menu.settings": "Paramètres...",
|
||||
"menu.tools": "&Outils",
|
||||
"menu.tools.fix_duplicate_files": "Réparer les entrées de fichiers en double",
|
||||
"menu.tools.fix_duplicate_files": "Réparer les entrées de &fichiers en double",
|
||||
"menu.tools.fix_ignored_entries": "Corriger les entrées &Ignorer",
|
||||
"menu.tools.fix_unlinked_entries": "Réparer les entrées de fichier non liée",
|
||||
"menu.view": "&Vues",
|
||||
"menu.view.decrease_thumbnail_size": "Rétrécir les vignettes",
|
||||
"menu.view.increase_thumbnail_size": "Agrandir les vignettes",
|
||||
"menu.view.library_info": "Bibliothèque &Information",
|
||||
"menu.window": "Fenêtre",
|
||||
"namespace.create.description": "Les namespaces sont utilisés par TagStudio pour séparer les groupes d'éléments tels que les Tags et les couleurs de manière à faciliter leur exportation et leur partage. Les namespace avec des noms commençant par « tagstudio » sont réservés par TagStudio pour un usage interne.",
|
||||
"namespace.create.description_color": "Les tags utilise les namespace pour regrouper plusieurs couleurs. Toutes les couleurs personnalisées doivent être ajoutées à un namespace.",
|
||||
|
||||
@@ -40,29 +40,35 @@
|
||||
"entries.duplicate.merge.label": "Egyező elemek egyesítése folyamatban…",
|
||||
"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.",
|
||||
"entries.generic.refresh_alt": "&Frissítés",
|
||||
"entries.generic.remove.removing": "Elemek törlése",
|
||||
"entries.generic.remove.removing_count": "{count} elem eltávolítása folyamatban…",
|
||||
"entries.ignored.description": "Fájlelemek akkor lehetnek „figyelmen kívül hagyva”, ha azelőtt vették fel őket a könyvtárba, mielőtt a mindenkori szabályok szerint („.ts_ignore” fájl) kizárásra kerültek volna. A szabályok frissítésekor ezek a fájlok megmaradnak a könyvtárban a véletlen törlés kiküszöbölése érdekében.",
|
||||
"entries.ignored.ignored_count": "Figyelmen kívül hagyott elemek: {count}",
|
||||
"entries.ignored.remove": "Figyelmen kívül hagyott elemek eltávolítása",
|
||||
"entries.ignored.remove_alt": "&Figyelmen kívül hagyott elemek eltávolítása",
|
||||
"entries.ignored.scanning": "Figyelmen kívül hagyott elemek keresése a könyvtárban…",
|
||||
"entries.ignored.title": "Figyelmen kívül hagyott elemek javítása",
|
||||
"entries.mirror": "&Tükrözés",
|
||||
"entries.mirror.confirmation": "Biztosan tükrözni akarja az alábbi adatokat {count} különböző elemre?",
|
||||
"entries.mirror.label": "{total}/{idx} elem tükrözése folyamatban…",
|
||||
"entries.mirror.title": "Elemek tükrözése",
|
||||
"entries.mirror.window_title": "Elemek tükrözése",
|
||||
"entries.remove.plural.confirm": "Biztosan el akarja távolítani ezt a(z) <b>{count}</b> elemet a könyvtárból? A lemezen található fájl nem lesz törölve.",
|
||||
"entries.remove.singular.confirm": "Biztosan el akarja távolítani ezt az elemet a könyvtárból? A lemezen található fájl nem lesz törölve.",
|
||||
"entries.running.dialog.new_entries": "{total} új elem felvétele folyamatban…",
|
||||
"entries.running.dialog.title": "Új elemek felvétele",
|
||||
"entries.tags": "Címkék",
|
||||
"entries.unlinked.delete": "Kapcsolat nélküli elemek törlése",
|
||||
"entries.unlinked.delete.confirm": "Biztosan törölni akarja az alábbi {count} elemet?",
|
||||
"entries.unlinked.delete.deleting": "Elemek törlése",
|
||||
"entries.unlinked.delete.deleting_count": "{count}/{idx}. kapcsolat nélküli elem törlése folyamatban…",
|
||||
"entries.unlinked.delete_alt": "&Kapcsolat nélküli elemek törlése",
|
||||
"entries.unlinked.description": "A könyvtár minden eleme egy fájllal van összekapcsolva a számítógépen. Ha egy kapcsolt fájl a TagSudión kívül áthelyezésre vagy törésre kerül, akkor ez a kapcsolat megszakad.<br><br>Ezeket a kapcsolat nélküli elemeket a program megpróbálhatja automatikusan megkeresni, de Ön is kézileg újra összekapcsolhatja vagy törölheti őket.",
|
||||
"entries.unlinked.missing_count.none": "Kapcsolat nélküli elemek: 0",
|
||||
"entries.unlinked.missing_count.some": "Kapcsolat nélküli elemek: {count}",
|
||||
"entries.unlinked.refresh_all": "&Az összes frissítése",
|
||||
"entries.unlinked.relink.attempting": "{missing_count}/{idx} elem újra összekapcsolásának megkísérlése; {fixed_count} elem sikeresen újra összekapcsolva",
|
||||
"entries.unlinked.relink.attempting": "{unlinked_count}/{index} elem újra összekapcsolásának megkísérlése; {fixed_count} elem sikeresen újra összekapcsolva",
|
||||
"entries.unlinked.relink.manual": "Új&ra összekapcsolás kézileg",
|
||||
"entries.unlinked.relink.title": "Elemek újra összekapcsolása",
|
||||
"entries.unlinked.remove": "Kapcsolat nélküli elemek eltávolítása",
|
||||
"entries.unlinked.remove_alt": "&Kapcsolat nélküli elemek eltávolítása",
|
||||
"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",
|
||||
"entries.unlinked.unlinked_count": "Kapcsolat nélküli elemek: {count}",
|
||||
"ffmpeg.missing.description": "Az FFmpeg és/vagy az FFprobe nem található. Az FFmpeg megléte szükséges a videó- és hangfájlok lejátszásához és a miniatűrök megjelenítéséhez.",
|
||||
"ffmpeg.missing.status": "{ffmpeg}: {ffmpeg_status}<br>{ffprobe}: {ffprobe_status}",
|
||||
"field.copy": "Mező &másolása",
|
||||
@@ -115,17 +121,21 @@
|
||||
"generic.missing": "Nem található",
|
||||
"generic.navigation.back": "Vissza",
|
||||
"generic.navigation.next": "Tovább",
|
||||
"generic.no": "Nem",
|
||||
"generic.none": "Nincs",
|
||||
"generic.overwrite": "Felülírás",
|
||||
"generic.overwrite_alt": "&Felülírás",
|
||||
"generic.paste": "Beillesztés",
|
||||
"generic.recent_libraries": "Legutóbbi könyvtárak",
|
||||
"generic.remove": "Eltávolítás",
|
||||
"generic.remove_alt": "&Eltávolítás",
|
||||
"generic.rename": "Átnevezés",
|
||||
"generic.rename_alt": "&Átnevezés",
|
||||
"generic.reset": "Alaphelyzet",
|
||||
"generic.save": "Mentés",
|
||||
"generic.skip": "Kihagyás",
|
||||
"generic.skip_alt": "&Kihagyás",
|
||||
"generic.yes": "Igen",
|
||||
"home.search": "Keresés",
|
||||
"home.search_entries": "Tételek keresése",
|
||||
"home.search_library": "Keresés a könyvtárban",
|
||||
@@ -136,6 +146,7 @@
|
||||
"home.thumbnail_size.medium": "Közepes miniatűrök",
|
||||
"home.thumbnail_size.mini": "Pici miniatűrök",
|
||||
"home.thumbnail_size.small": "Kicsi miniatűrök",
|
||||
"ignore.open_file": "„{ts_ignore}” fájl megjelenítése a lemezen",
|
||||
"json_migration.checking_for_parity": "Paritás ellenőrzése folyamatban…",
|
||||
"json_migration.creating_database_tables": "SQL-adatbázis táblázatainak létrehozása folyamatban…",
|
||||
"json_migration.description": "<br>A könyvtárátalakítási folyamat megkezdése és az eredmény előnézete. Az új könyvtár az „Átalakítás befejezése” gomb megnyomásáig <i>nem</i> lesz használatba véve.<br><br>A könyvtár adatai változatlanok maradnak vagy egy „Egységesítve” címkével lesznek felruházva. A nem egyező adatok vörösen lesznek megjelenítve és egy „<b>(!)</b>” szimbólummal lesznek ellátva.<br><center></i>Ez a folyamat nagyobb könyvtárak esetén akár több percig is eltarthat.</i><center>",
|
||||
@@ -161,9 +172,6 @@
|
||||
"json_migration.title.new_lib": "<h2>9.5 és afölötti könyvtár</h2>",
|
||||
"json_migration.title.old_lib": "<h2>9.4-es könyvtár</h2>",
|
||||
"landing.open_create_library": "Könyvtár meg&nyitása/létrehozása {shortcut}",
|
||||
"library_info.stats.entries": "Elemek:",
|
||||
"library_info.stats.fields": "Mezők:",
|
||||
"library_info.stats.tags": "Címkék:",
|
||||
"library.field.add": "Új mező",
|
||||
"library.field.confirm_remove": "Biztosan el akarja távolítani a(z) „{name}”-mezőt?",
|
||||
"library.field.mixed_data": "Kevert adatok",
|
||||
@@ -175,6 +183,21 @@
|
||||
"library.refresh.scanning_preparing": "Új fájlok keresése a mappákban…\nElőkészítés…",
|
||||
"library.refresh.title": "Könyvtárak frissítése",
|
||||
"library.scan_library.title": "Könyvtár vizsgálata",
|
||||
"library_info.cleanup": "Megtisztítás",
|
||||
"library_info.cleanup.backups": "Könyvtár biztonsági mentései:",
|
||||
"library_info.cleanup.dupe_files": "Egyező fájlok:",
|
||||
"library_info.cleanup.ignored": "Figyelmen kívül hagyott elemek:",
|
||||
"library_info.cleanup.legacy_json": "Megmaradt örökölt könyvtár:",
|
||||
"library_info.cleanup.unlinked": "Kapcsolat nélküli elemek:",
|
||||
"library_info.stats": "Statisztikák",
|
||||
"library_info.stats.colors": "Címkeszínek:",
|
||||
"library_info.stats.entries": "Elemek:",
|
||||
"library_info.stats.fields": "Mezők:",
|
||||
"library_info.stats.macros": "Makrók:",
|
||||
"library_info.stats.namespaces": "Névterek:",
|
||||
"library_info.stats.tags": "Címkék:",
|
||||
"library_info.title": "„{library_dir}” könyvtár",
|
||||
"library_info.version": "Könyvtárformátum verziója: {version}",
|
||||
"library_object.name": "Megnevezés",
|
||||
"library_object.name_required": "Megnevezés (kötelező)",
|
||||
"library_object.slug": "Azonosító-helyőrző",
|
||||
@@ -196,6 +219,7 @@
|
||||
"menu.file.missing_library.message": "A(z) „{library}”-könyvtár nem található.",
|
||||
"menu.file.missing_library.title": "Hiányzó könyvtár",
|
||||
"menu.file.new_library": "Új könyvtár",
|
||||
"menu.file.open_backups_folder": "Biztonsági mentések mappájának megnyitása",
|
||||
"menu.file.open_create_library": "Könyvtár meg&nyitása/létrehozása",
|
||||
"menu.file.open_library": "Könyvtár megnyitása",
|
||||
"menu.file.open_recent_library": "&Legutóbbi könyvtárak",
|
||||
@@ -210,10 +234,12 @@
|
||||
"menu.settings": "&Beállítások…",
|
||||
"menu.tools": "&Eszközök",
|
||||
"menu.tools.fix_duplicate_files": "&Egyező fájlok egyesítése",
|
||||
"menu.tools.fix_ignored_entries": "Figyelmen &kívül hagyott elemek javítása",
|
||||
"menu.tools.fix_unlinked_entries": "Kapcsolat &nélküli elemek javítása",
|
||||
"menu.view": "&Nézet",
|
||||
"menu.view.decrease_thumbnail_size": "Indexkép méretének csökkentése",
|
||||
"menu.view.increase_thumbnail_size": "Indexkép méretének növelése",
|
||||
"menu.view.library_info": "&Könyvtárinformáció",
|
||||
"menu.window": "&Ablak",
|
||||
"namespace.create.description": "A TagStudio névterekkel különíti el az adatcsoportokat, mint a címkék és a színek, így azok könnyen exportálhatóak és megoszthatóak. A „tagstudio”-val kezdődő névterek belső használatra vannak lefoglalva.",
|
||||
"namespace.create.description_color": "Minden szín névterekbe van foglalva, amelyek színpalettaként viselkednek. Minden egyéni színt névtérbe kell foglalni.",
|
||||
@@ -247,6 +273,12 @@
|
||||
"settings.restart_required": "A módosítások érvénybeléptetéséhez<br>újra kell indítani a TagStudiót.",
|
||||
"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",
|
||||
"settings.splash.label": "Indítókép",
|
||||
"settings.splash.option.classic": "Klasszikus (9.0)",
|
||||
"settings.splash.option.default": "Alapértelmezett",
|
||||
"settings.splash.option.goo_gears": "Nyílt forráskódú (9.4)",
|
||||
"settings.splash.option.ninety_five": "'95-ös (9.5)",
|
||||
"settings.splash.option.random": "Véletlenszerű",
|
||||
"settings.tag_click_action.add_to_search": "Címke hozzáfűzése a kereséshez",
|
||||
"settings.tag_click_action.label": "Címkére kattintási művelet",
|
||||
"settings.tag_click_action.open_edit": "Címke szerkesztése",
|
||||
|
||||
@@ -40,29 +40,24 @@
|
||||
"entries.duplicate.merge.label": "重複エントリを統合しています...",
|
||||
"entries.duplicate.refresh": "重複エントリを最新の状態にする",
|
||||
"entries.duplicates.description": "重複エントリとは、ディスク上の同じファイルを指す複数のエントリを指します。これらをマージすると、すべての重複エントリのタグとメタデータが1つのまとまったエントリに統合されます。TagStudio の外部にあるファイル自体の複製である「重複ファイル」と混同しないようにご注意ください。",
|
||||
"entries.generic.remove.removing": "エントリの削除",
|
||||
"entries.mirror": "ミラー(&M)",
|
||||
"entries.mirror.confirmation": "以下の {count} 件のエントリをミラーリングしてもよろしいですか?",
|
||||
"entries.mirror.label": "{total} 件中 {idx} 件のエントリをミラーリングしています...",
|
||||
"entries.mirror.title": "エントリをミラー",
|
||||
"entries.mirror.window_title": "エントリをミラー",
|
||||
"entries.remove.plural.confirm": "以下の {count} 件のエントリを削除してもよろしいですか?",
|
||||
"entries.running.dialog.new_entries": "{total} 件の新しいファイル エントリを追加しています...",
|
||||
"entries.running.dialog.title": "新しいファイルエントリを追加",
|
||||
"entries.tags": "タグ",
|
||||
"entries.unlinked.delete": "未リンクのエントリを削除",
|
||||
"entries.unlinked.delete.confirm": "以下の {count} 件のエントリを削除してもよろしいですか?",
|
||||
"entries.unlinked.delete.deleting": "エントリの削除",
|
||||
"entries.unlinked.delete.deleting_count": "{count} 件中 {idx} 件の未リンクのエントリを削除しています",
|
||||
"entries.unlinked.delete_alt": "未リンクのエントリを削除(&L)",
|
||||
"entries.unlinked.description": "ライブラリの各エントリは、ディレクトリ内のファイルにリンクされています。エントリにリンクされたファイルがTagStudio以外で移動または削除された場合、そのエントリは未リンクとして扱われます。<br><br>未リンクのエントリは、ディレクトリを検索して自動的に再リンクすることも、必要に応じて削除することもできます。",
|
||||
"entries.unlinked.missing_count.none": "未リンクのエントリを削除",
|
||||
"entries.unlinked.missing_count.some": "未リンクのエントリ数: {count}",
|
||||
"entries.unlinked.refresh_all": "すべて再読み込み(&R)",
|
||||
"entries.unlinked.relink.attempting": "{missing_count} 件中 {idx} 件の項目を再リンク中、{fixed_count} 件を正常に再リンクしました",
|
||||
"entries.unlinked.relink.attempting": "{unlinked_count} 件中 {index} 件の項目を再リンク中、{fixed_count} 件を正常に再リンクしました",
|
||||
"entries.unlinked.relink.manual": "手動で再リンク(&M)",
|
||||
"entries.unlinked.relink.title": "エントリの再リンク",
|
||||
"entries.unlinked.scanning": "未リンクのエントリをライブラリ内でスキャンしています...",
|
||||
"entries.unlinked.search_and_relink": "検索して再リンク(&S)",
|
||||
"entries.unlinked.title": "未リンクのエントリを修正",
|
||||
"entries.unlinked.unlinked_count": "未リンクのエントリ数: {count}",
|
||||
"ffmpeg.missing.description": "FFmpeg または FFprobe が見つかりません。マルチメディアの再生とサムネイルの表示には FFmpeg のインストールが必要です。",
|
||||
"ffmpeg.missing.status": "{ffmpeg}: {ffmpeg_status}<br>{ffprobe}: {ffprobe_status}",
|
||||
"field.copy": "フィールドをコピー",
|
||||
@@ -161,9 +156,6 @@
|
||||
"json_migration.title.new_lib": "<h2>v9.5+ ライブラリ</h2>",
|
||||
"json_migration.title.old_lib": "<h2>v9.4 ライブラリ</h2>",
|
||||
"landing.open_create_library": "ライブラリを開く/作成する {shortcut}",
|
||||
"library_info.stats.entries": "エントリ:",
|
||||
"library_info.stats.fields": "フィールド:",
|
||||
"library_info.stats.tags": "タグ:",
|
||||
"library.field.add": "フィールドの追加",
|
||||
"library.field.confirm_remove": "「{name}」フィールドを削除してもよろしいですか?",
|
||||
"library.field.mixed_data": "混在データ",
|
||||
@@ -175,6 +167,9 @@
|
||||
"library.refresh.scanning_preparing": "新しいファイルを検索中...\n準備中...",
|
||||
"library.refresh.title": "ディレクトリを更新しています",
|
||||
"library.scan_library.title": "ライブラリをスキャンしています",
|
||||
"library_info.stats.entries": "エントリ:",
|
||||
"library_info.stats.fields": "フィールド:",
|
||||
"library_info.stats.tags": "タグ:",
|
||||
"library_object.name": "名前",
|
||||
"library_object.name_required": "名前 (必須)",
|
||||
"library_object.slug": "ID スラッグ",
|
||||
|
||||
@@ -40,29 +40,24 @@
|
||||
"entries.duplicate.merge.label": "Fletter duplikatoppføringer…",
|
||||
"entries.duplicate.refresh": "Oppdater Duplikate Oppføringer",
|
||||
"entries.duplicates.description": "Duplikate oppføringer er definert som flere oppføringer som peker til samme fil på disken. Å slå disse sammen vil kombinere etikettene og metadataen fra alle duplikater til én enkel oppføring. Disse må ikke forveksles med \"duplikate filer\", som er duplikater av selve filene utenfor TagStudio.",
|
||||
"entries.generic.remove.removing": "Sletter Oppføringer",
|
||||
"entries.mirror": "Speil",
|
||||
"entries.mirror.confirmation": "Er du sikker på at du vil speile følgende {count} Oppføringer?",
|
||||
"entries.mirror.label": "Speiler {idx}/{total} Oppføringer...",
|
||||
"entries.mirror.title": "Speiler Oppføringer",
|
||||
"entries.mirror.window_title": "Speil Oppføringer",
|
||||
"entries.remove.plural.confirm": "Er du sikker på at di voø slette følgende {count} oppføringer?",
|
||||
"entries.running.dialog.new_entries": "Legger til {total} Nye Filoppføringer...",
|
||||
"entries.running.dialog.title": "Legger til Nye Filoppføringer",
|
||||
"entries.tags": "Etiketter",
|
||||
"entries.unlinked.delete": "Slett Frakoblede Oppføringer",
|
||||
"entries.unlinked.delete.confirm": "Er du sikker på at di voø slette følgende {count} oppføringer?",
|
||||
"entries.unlinked.delete.deleting": "Sletter Oppføringer",
|
||||
"entries.unlinked.delete.deleting_count": "Sletter {idx}/{count} ulenkede oppføringer",
|
||||
"entries.unlinked.delete_alt": "&Slett Frakoblede Oppføringer",
|
||||
"entries.unlinked.description": "Hver biblioteksoppføring er koblet til en fil i en av dine mapper. Hvis en fil koblet til en oppføring er flyttet eller slettet utenfor TagStudio, så er den sett på som frakoblet.<br><br>Frakoblede oppføringer kan bli automatisk gjenkoblet ved å søke i mappene dine eller slettet om det er ønsket.",
|
||||
"entries.unlinked.missing_count.none": "Frakoblede Oppføringer: N/A",
|
||||
"entries.unlinked.missing_count.some": "Frakoblede Oppføringer: {count}",
|
||||
"entries.unlinked.refresh_all": "Gjenoppfrisk alle",
|
||||
"entries.unlinked.relink.attempting": "Forsøker å Gjenkoble {idx}/{missing_count} Oppføringer, {fixed_count} Klart Gjenkoblet",
|
||||
"entries.unlinked.relink.attempting": "Forsøker å Gjenkoble {index}/{unlinked_count} Oppføringer, {fixed_count} Klart Gjenkoblet",
|
||||
"entries.unlinked.relink.manual": "&Manuell Gjenkobling",
|
||||
"entries.unlinked.relink.title": "Gjenkobler Oppføringer",
|
||||
"entries.unlinked.scanning": "Skanner bibliotek for ulenkede oppføringer …",
|
||||
"entries.unlinked.search_and_relink": "&Søk && Gjenkobl",
|
||||
"entries.unlinked.title": "Fiks ulenkede oppføringer",
|
||||
"entries.unlinked.unlinked_count": "Frakoblede Oppføringer: {count}",
|
||||
"ffmpeg.missing.description": "FFmpeg og/eller FFprobe ble ikke funnet. FFmpeg er påkrevd for flermediell gjenspilling og miniatyrbilde.",
|
||||
"ffmpeg.missing.status": "{ffmpeg}: {ffmpeg_status}<br>{ffprobe}: {ffprobe_status}",
|
||||
"field.copy": "Kopier Felt",
|
||||
@@ -161,9 +156,6 @@
|
||||
"json_migration.title.new_lib": "<h2>v9.5+ Bibliotek</h2>",
|
||||
"json_migration.title.old_lib": "<h2>v9.4 Bibliotek</h2>",
|
||||
"landing.open_create_library": "Åpne/Lag nytt Bibliotek {shortcut}",
|
||||
"library_info.stats.entries": "Oppføringer:",
|
||||
"library_info.stats.fields": "Felter:",
|
||||
"library_info.stats.tags": "Etiketter:",
|
||||
"library.field.add": "Legg til felt",
|
||||
"library.field.confirm_remove": "Fjern dette «\"{name}\"»-feltet?",
|
||||
"library.field.mixed_data": "Blandet data",
|
||||
@@ -175,6 +167,9 @@
|
||||
"library.refresh.scanning_preparing": "Skanner Mapper for Nye Filer...\nForbereder...",
|
||||
"library.refresh.title": "Oppdaterer Mapper",
|
||||
"library.scan_library.title": "Skanning av bibliotek",
|
||||
"library_info.stats.entries": "Oppføringer:",
|
||||
"library_info.stats.fields": "Felter:",
|
||||
"library_info.stats.tags": "Etiketter:",
|
||||
"library_object.name": "Navn",
|
||||
"library_object.name_required": "Navn (Påkrevd)",
|
||||
"library_object.slug": "ID Slug",
|
||||
|
||||
@@ -40,29 +40,24 @@
|
||||
"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.",
|
||||
"entries.generic.remove.removing": "Usuwanie wpisów",
|
||||
"entries.mirror": "&Odzwierciedl",
|
||||
"entries.mirror.confirmation": "Jesteś pewien że chcesz odzwierciedlić następujące {count} wpisy?",
|
||||
"entries.mirror.label": "Odzwierciedlanie {idx}/{total} wpisów...",
|
||||
"entries.mirror.title": "Odzwierciedlanie wpisów",
|
||||
"entries.mirror.window_title": "Odzwierciedl wpisy",
|
||||
"entries.remove.plural.confirm": "Jesteś pewien że chcesz usunąć następujące {count} wpisy?",
|
||||
"entries.running.dialog.new_entries": "Dodawanie {total} nowych wpisów plików...",
|
||||
"entries.running.dialog.title": "Dodawanie nowych wpisów plików",
|
||||
"entries.tags": "Tagi",
|
||||
"entries.unlinked.delete": "Usuń odłączone wpisy",
|
||||
"entries.unlinked.delete.confirm": "Jesteś pewien że chcesz usunąć następujące {count} wpisy?",
|
||||
"entries.unlinked.delete.deleting": "Usuwanie wpisów",
|
||||
"entries.unlinked.delete.deleting_count": "Usuwanie {idx}/{count} odłączonych wpisów",
|
||||
"entries.unlinked.delete_alt": "Usuń odłączone wpisy",
|
||||
"entries.unlinked.description": "Każdy wpis w bibliotece jest połączony z plikiem w jednym z twoich katalogów. Jeśli połączony plik jest przeniesiony poza TagStudio albo usunięty to jest uważany za odłączony.<br><br>Odłączone wpisy mogą być automatycznie połączone ponownie przez szukanie twoich katalogów, ręczne ponowne łączenie przez użytkownika lub usunięte jeśli zajdzie taka potrzeba.",
|
||||
"entries.unlinked.missing_count.none": "Odłączone wpisy: brak",
|
||||
"entries.unlinked.missing_count.some": "Odłączone wpisy: {count}",
|
||||
"entries.unlinked.refresh_all": "&Odśwież wszystko",
|
||||
"entries.unlinked.relink.attempting": "Próbowanie ponownego łączenia {idx}/{missing_count} wpisów, {fixed_count} poprawnie połączono ponownie",
|
||||
"entries.unlinked.relink.attempting": "Próbowanie ponownego łączenia {index}/{unlinked_count} wpisów, {fixed_count} poprawnie połączono ponownie",
|
||||
"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",
|
||||
"entries.unlinked.unlinked_count": "Odłączone wpisy: {count}",
|
||||
"ffmpeg.missing.description": "Nie odnaleziono FFmpeg lub FFprobe. FFmpeg jest wymagany do odtwarzania multimediów i do wyświetlania miniaturek.",
|
||||
"ffmpeg.missing.status": "{ffmpeg}: {ffmpeg_status}<br>{ffprobe}: {ffprobe_status}",
|
||||
"field.copy": "Skopiuj pole",
|
||||
@@ -159,9 +154,6 @@
|
||||
"json_migration.title.new_lib": "<h2>Biblioteka v9.5+ </h2>",
|
||||
"json_migration.title.old_lib": "<h2>Biblioteka v9.4</h2>",
|
||||
"landing.open_create_library": "Otwórz/Stwórz bibliotekę {shortcut}",
|
||||
"library_info.stats.entries": "Wpisy:",
|
||||
"library_info.stats.fields": "Pola:",
|
||||
"library_info.stats.tags": "Tagi:",
|
||||
"library.field.add": "Dodaj pole",
|
||||
"library.field.confirm_remove": "Jesteś pewien że chcesz usunąć pole \"{name}\" ?",
|
||||
"library.field.mixed_data": "Mieszane dane",
|
||||
@@ -172,6 +164,9 @@
|
||||
"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",
|
||||
"library_info.stats.entries": "Wpisy:",
|
||||
"library_info.stats.fields": "Pola:",
|
||||
"library_info.stats.tags": "Tagi:",
|
||||
"library_object.name": "Nazwa",
|
||||
"library_object.name_required": "Nazwa (wymagana)",
|
||||
"macros.running.dialog.new_entries": "Stosowanie skonfigurowanych makr na {count}/{total} nowych wpisach plików...",
|
||||
|
||||
@@ -39,29 +39,24 @@
|
||||
"entries.duplicate.merge.label": "A Mesclar Elementos Duplicados...",
|
||||
"entries.duplicate.refresh": "Atualizar Registos Duplicados",
|
||||
"entries.duplicates.description": "Registos duplicados são definidas como múltiplos registos que levam ao mesmo ficheiro no disco. Mesclar estes registos irá combinar as tags e metadados de todas as duplicatas num único registo consolidado. Não confundir com \"Ficheiros Duplicados\" que são duplicatas dos seus ficheiros fora do TagStudio.",
|
||||
"entries.generic.remove.removing": "A Apagar Registos",
|
||||
"entries.mirror": "&Espelho",
|
||||
"entries.mirror.confirmation": "Tem certeza que deseja espelhar os seguintes {count} registos?",
|
||||
"entries.mirror.label": "A Espelhar {idx}/{total} Registos...",
|
||||
"entries.mirror.title": "A Espelhar Registos",
|
||||
"entries.mirror.window_title": "Espelhar Registos",
|
||||
"entries.remove.plural.confirm": "Tem certeza que deseja apagar os seguintes {count} registos ?",
|
||||
"entries.running.dialog.new_entries": "A Adicionar {total} Novos Registos de Ficheiros...",
|
||||
"entries.running.dialog.title": "A Adicionar Novos Registos de Ficheiros",
|
||||
"entries.tags": "Tags",
|
||||
"entries.unlinked.delete": "Apagar Registos não Referenciados",
|
||||
"entries.unlinked.delete.confirm": "Tem certeza que deseja apagar os seguintes {count} registos ?",
|
||||
"entries.unlinked.delete.deleting": "A Apagar Registos",
|
||||
"entries.unlinked.delete.deleting_count": "A Apagar {idx}/{count} Registos Não Ligadas",
|
||||
"entries.unlinked.delete_alt": "De&letar Registos Não Ligados",
|
||||
"entries.unlinked.description": "Cada registo na biblioteca faz referência à um ficheiro numa das suas pastas. Se um ficheiro referenciado à uma entrada for movido ou apagado fora do TagStudio, ele é depois considerado não-referenciado.<br><br>Registos não-referenciados podem ser automaticamente referenciados por pesquisas nos seus diretórios, manualmente pelo utilizador, ou apagado se for desejado.",
|
||||
"entries.unlinked.missing_count.none": "Registos Não Referenciados: N/A",
|
||||
"entries.unlinked.missing_count.some": "Registos não referenciados: {count}",
|
||||
"entries.unlinked.refresh_all": "&Atualizar Tudo",
|
||||
"entries.unlinked.relink.attempting": "A tentar referenciar {idx}/{missing_count} Registos, {fixed_count} Referenciados com Sucesso",
|
||||
"entries.unlinked.relink.attempting": "A tentar referenciar {index}/{unlinked_count} Registos, {fixed_count} Referenciados com Sucesso",
|
||||
"entries.unlinked.relink.manual": "&Referência Manual",
|
||||
"entries.unlinked.relink.title": "A Referenciar Registos",
|
||||
"entries.unlinked.scanning": "A escanear biblioteca por registos não referenciados...",
|
||||
"entries.unlinked.search_and_relink": "&Pesquisar && Referenciar",
|
||||
"entries.unlinked.title": "Corrigir Registos Não Referenciados",
|
||||
"entries.unlinked.unlinked_count": "Registos Não Referenciados: {count}",
|
||||
"ffmpeg.missing.status": "{ffmpeg}: {ffmpeg_status}<br>{ffprobe}: {ffprobe_status}",
|
||||
"field.copy": "Copiar Campo",
|
||||
"field.edit": "Editar Campo",
|
||||
|
||||
@@ -37,29 +37,24 @@
|
||||
"entries.duplicate.merge.label": "Mesclando Itens Duplicados...",
|
||||
"entries.duplicate.refresh": "Atualizar Registros Duplicados",
|
||||
"entries.duplicates.description": "Registros duplicados são definidas como multiplos registros que levam ao mesmo arquivo no disco. Mesclar esses registros irá combinar as tags e metadados de todas as duplicatas em um único registro consolidado. Não confundir com \"Arquivos Duplicados\" que são duplicatas dos seus arquivos fora do TagStudio.",
|
||||
"entries.generic.remove.removing": "Deletando Registros",
|
||||
"entries.mirror": "&Espelho",
|
||||
"entries.mirror.confirmation": "Tem certeza que você deseja espelhar os seguintes {count} registros?",
|
||||
"entries.mirror.label": "Espelhando {idx}/{total} Registros...",
|
||||
"entries.mirror.title": "Espelhando Registros",
|
||||
"entries.mirror.window_title": "Espelhar Registros",
|
||||
"entries.remove.plural.confirm": "Tem certeza que deseja deletar os seguintes {count} Registros ?",
|
||||
"entries.running.dialog.new_entries": "Adicionando {total} Novos Registros de Arquivos...",
|
||||
"entries.running.dialog.title": "Adicionando Novos Registros de Arquivos",
|
||||
"entries.tags": "Tags",
|
||||
"entries.unlinked.delete": "Deletar Registros não Referenciados",
|
||||
"entries.unlinked.delete.confirm": "Tem certeza que deseja deletar os seguintes {count} Registros ?",
|
||||
"entries.unlinked.delete.deleting": "Deletando Registros",
|
||||
"entries.unlinked.delete.deleting_count": "Deletando {idx}/{count} Registros Não Linkadas",
|
||||
"entries.unlinked.delete_alt": "De&letar Registros Não Linkados",
|
||||
"entries.unlinked.description": "Cada registro na biblioteca faz referência à um arquivo em uma de suas pastas. Se um arquivo referenciado à uma entrada for movido ou deletado fora do TagStudio, ele é então considerado não-referenciado.<br><br>Registros não-referenciados podem ser automaticamente referenciados por buscas nos seus diretórios, manualmente pelo usuário, ou deletado se for desejado.",
|
||||
"entries.unlinked.missing_count.none": "Registros Não Referenciados: N/A",
|
||||
"entries.unlinked.missing_count.some": "Registros não referenciados: {count}",
|
||||
"entries.unlinked.refresh_all": "&Atualizar Tudo",
|
||||
"entries.unlinked.relink.attempting": "Tentando referenciar {idx}/{missing_count} Registros, {fixed_count} Referenciados com Sucesso",
|
||||
"entries.unlinked.relink.attempting": "Tentando referenciar {index}/{unlinked_count} Registros, {fixed_count} Referenciados com Sucesso",
|
||||
"entries.unlinked.relink.manual": "&Referência Manual",
|
||||
"entries.unlinked.relink.title": "Referenciando Registros",
|
||||
"entries.unlinked.scanning": "Escaneando bibliotecada em busca de registros não referenciados...",
|
||||
"entries.unlinked.search_and_relink": "&Buscar && Referenciar",
|
||||
"entries.unlinked.title": "Corrigir Registros Não Referenciados",
|
||||
"entries.unlinked.unlinked_count": "Registros Não Referenciados: {count}",
|
||||
"field.copy": "Copiar Campo",
|
||||
"field.edit": "Editar Campo",
|
||||
"field.paste": "Colar Campo",
|
||||
@@ -146,9 +141,6 @@
|
||||
"json_migration.title.new_lib": "<h2>Biblioteca v9.5+</h2>",
|
||||
"json_migration.title.old_lib": "<h2>Biblioteca v9.4</h2>",
|
||||
"landing.open_create_library": "Abrir/Criar Biblioteca {shortcut}",
|
||||
"library_info.stats.entries": "Registros:",
|
||||
"library_info.stats.fields": "Campos:",
|
||||
"library_info.stats.tags": "Tags:",
|
||||
"library.field.add": "Adicionar Campo",
|
||||
"library.field.confirm_remove": "Você tem certeza de que quer remover o campo \"{name}\"?",
|
||||
"library.field.mixed_data": "Dados Mistos",
|
||||
@@ -160,6 +152,9 @@
|
||||
"library.refresh.scanning_preparing": "Escaneando Diretórios por Novos Arquivos...\nPreparando...",
|
||||
"library.refresh.title": "Atualizando Pastas",
|
||||
"library.scan_library.title": "Escaneando Biblioteca",
|
||||
"library_info.stats.entries": "Registros:",
|
||||
"library_info.stats.fields": "Campos:",
|
||||
"library_info.stats.tags": "Tags:",
|
||||
"library_object.name": "Nome",
|
||||
"library_object.name_required": "Nome (Obrigatório)",
|
||||
"macros.running.dialog.new_entries": "Executando Macros Configurados nos {count}/{total} Novos Registros de Arquivos...",
|
||||
|
||||
@@ -40,29 +40,24 @@
|
||||
"entries.duplicate.merge.label": "Visk sama shiruzmakaban ima...",
|
||||
"entries.duplicate.refresh": "Gotova sama shiruzmakaban gen",
|
||||
"entries.duplicates.description": "Mverm fu shiruzmakaban implajena na plus ka ein shiruzmakaban ke tsunaga na sama mlafu na shiruzmabaksu. Na visk afto, zol festa festaretol au mlafushiruzma al mverm fu mlafu kara ine ein stuur shiruzmakaban. Hej nai sama \"mverm fu mlafu\", ke mverm fu mlafu fu du, ekso TagStudio.",
|
||||
"entries.generic.remove.removing": "Keste shiruzmakaban ima",
|
||||
"entries.mirror": "&Maha melon fu",
|
||||
"entries.mirror.confirmation": "Du mahatsa melon fu afto {count} shiruzmakaban?",
|
||||
"entries.mirror.label": "Maha melon fu {idx}/{total} shiruzmakaban ima...",
|
||||
"entries.mirror.title": "Maha melon fu shiruzmakaban ima",
|
||||
"entries.mirror.window_title": "Maha melon fu shiruzmakaban",
|
||||
"entries.remove.plural.confirm": "Du kestetsa afto {count} shiruzmakaban?",
|
||||
"entries.running.dialog.new_entries": "Nasii {total} neo shiruzmakaban fu mlafu ima...",
|
||||
"entries.running.dialog.title": "Nasii neo shiruzmakaban fu mlafu ima",
|
||||
"entries.tags": "Festaretol",
|
||||
"entries.unlinked.delete": "Keste tsunaganaijena shiruzmakaban",
|
||||
"entries.unlinked.delete.confirm": "Du kestetsa afto {count} shiruzmakaban?",
|
||||
"entries.unlinked.delete.deleting": "Keste shiruzmakaban ima",
|
||||
"entries.unlinked.delete.deleting_count": "Keste {idx}/{count} tsunaganaijena shiruzmakaban ima",
|
||||
"entries.unlinked.delete_alt": "Ke&ste tsunaganaijena shiruzmakaban",
|
||||
"entries.unlinked.description": "Tont shiruzmakaban fu mlafuhuomi tsunagajena na mlafu ine joku mlafukaban fu du. Li mlafu tsunagajena na shiruzmakaban ugokijena os kestejena ekso TagStudio, sit sore tsunaganaijena.<br><br>Tsunaganaijena shiruzmakaban deki tsunaga gen na suha per mlafukaban fu du os keste li du vil.",
|
||||
"entries.unlinked.missing_count.none": "Tsunaganaijena shiruzmakaban: Jamnai",
|
||||
"entries.unlinked.missing_count.some": "Tsunaganaijena shiruzmakaban: {count}",
|
||||
"entries.unlinked.refresh_all": "&Gotova al gen",
|
||||
"entries.unlinked.relink.attempting": "Iskat ima na tsunaga gen {idx}/{missing_count} shiruzmakaban, {fixed_count} tsunagajena gen",
|
||||
"entries.unlinked.relink.attempting": "Iskat ima na tsunaga gen {index}/{unlinked_count} shiruzmakaban, {fixed_count} tsunagajena gen",
|
||||
"entries.unlinked.relink.manual": "&Tsunaga gen mit hant",
|
||||
"entries.unlinked.relink.title": "Tsunaga shiruzmakaban gen",
|
||||
"entries.unlinked.scanning": "Suha mlafuhuomi ima per tsunaganaijena shiruzmakaban...",
|
||||
"entries.unlinked.search_and_relink": "&Suha &&Tsunaga gen",
|
||||
"entries.unlinked.title": "Fiks tsunaganaijena shiruzmakaban",
|
||||
"entries.unlinked.unlinked_count": "Tsunaganaijena shiruzmakaban: {count}",
|
||||
"ffmpeg.missing.description": "FFmpeg au/os FFprobe nai finnajena. TagStudio treng FFmpeg per mahase riso.",
|
||||
"ffmpeg.missing.status": "{ffmpeg}: {ffmpeg_status}<br>{ffprobe}: {ffprobe_status}",
|
||||
"field.copy": "Mverm shiruzmafal",
|
||||
@@ -157,9 +152,6 @@
|
||||
"json_migration.title.new_lib": "<h2>v9.5+ Mlafuhuomi</h2>",
|
||||
"json_migration.title.old_lib": "<h2>v9.4 Mlafuhuomi</h2>",
|
||||
"landing.open_create_library": "Auki/Maha mlafuhuomi {shortcut}",
|
||||
"library_info.stats.entries": "Shiruzmakaban:",
|
||||
"library_info.stats.fields": "Shiruzmafal:",
|
||||
"library_info.stats.tags": "Festaretol:",
|
||||
"library.field.add": "Nasii shiruzmafal",
|
||||
"library.field.confirm_remove": "Du kestetsa afto \"{name}\" shiruzmafal we?",
|
||||
"library.field.mixed_data": "Viskena shiruzma",
|
||||
@@ -171,6 +163,9 @@
|
||||
"library.refresh.scanning_preparing": "Taskama mlafukaban fu neo mlafu ima...\nGotova ima...",
|
||||
"library.refresh.title": "Gengotova al mlafukaban",
|
||||
"library.scan_library.title": "Taskama mlafuhuomi ima",
|
||||
"library_info.stats.entries": "Shiruzmakaban:",
|
||||
"library_info.stats.fields": "Shiruzmafal:",
|
||||
"library_info.stats.tags": "Festaretol:",
|
||||
"library_object.name": "Namae",
|
||||
"library_object.name_required": "Namae (Trengjena)",
|
||||
"library_object.slug": "ID Slug",
|
||||
|
||||
@@ -40,29 +40,24 @@
|
||||
"entries.duplicate.merge.label": "Объединение записей-дубликатов...",
|
||||
"entries.duplicate.refresh": "Обновить записи-дубликаты",
|
||||
"entries.duplicates.description": "Записи-дубликаты — это несколько записей, которые одновременно привязаны к одному файлу. Объединение таких дубликатов соединит все теги и мета данные из этих записей в одну. Записи-дубликаты не стоит путать с несколькими копиями самого файла, которые могут существовать вне TagStudio.",
|
||||
"entries.generic.remove.removing": "Удаление записей",
|
||||
"entries.mirror": "&Отзеркалить",
|
||||
"entries.mirror.confirmation": "Вы уверены, что хотите отзеркалить следующие {count} записей?",
|
||||
"entries.mirror.label": "Отзеркаливание {idx}/{total} записей...",
|
||||
"entries.mirror.title": "Отзеркаливание записей",
|
||||
"entries.mirror.window_title": "Отзеркалить записи",
|
||||
"entries.remove.plural.confirm": "Вы уверены, что хотите удалить {count} записей?",
|
||||
"entries.running.dialog.new_entries": "Добавление {total} новых записей...",
|
||||
"entries.running.dialog.title": "Добавление новых записей",
|
||||
"entries.tags": "Теги",
|
||||
"entries.unlinked.delete": "Удалить откреплённые записи",
|
||||
"entries.unlinked.delete.confirm": "Вы уверены, что хотите удалить {count} записей?",
|
||||
"entries.unlinked.delete.deleting": "Удаление записей",
|
||||
"entries.unlinked.delete.deleting_count": "Удаление {idx}/{count} откреплённых записей",
|
||||
"entries.unlinked.delete_alt": "&Удалить откреплённые записи",
|
||||
"entries.unlinked.description": "Каждая запись в библиотеке привязана к файлу, находящегося внутри той или иной папки. Если файл, к которому была привязана запись, был удалён или перемещён без использования TagStudio, то запись становиться \"откреплённой\".<br><br>Откреплённые записи могут быть прикреплены обратно автоматически, либо же удалены если в них нет надобности.",
|
||||
"entries.unlinked.missing_count.none": "Откреплённых записей: нет",
|
||||
"entries.unlinked.missing_count.some": "Откреплённых записей: {count}",
|
||||
"entries.unlinked.refresh_all": "&Обновить Всё",
|
||||
"entries.unlinked.relink.attempting": "Попытка перепривязать {idx}/{missing_count} записей, {fixed_count} привязано успешно",
|
||||
"entries.unlinked.relink.attempting": "Попытка перепривязать {index}/{unlinked_count} записей, {fixed_count} привязано успешно",
|
||||
"entries.unlinked.relink.manual": "&Ручная привязка",
|
||||
"entries.unlinked.relink.title": "Привязка записей",
|
||||
"entries.unlinked.scanning": "Сканирование библиотеки на наличие откреплённых записей...",
|
||||
"entries.unlinked.search_and_relink": "&Поиск и привязка",
|
||||
"entries.unlinked.title": "Исправить откреплённые записи",
|
||||
"entries.unlinked.unlinked_count": "Откреплённых записей: {count}",
|
||||
"ffmpeg.missing.description": "FFmpeg и/или FFprobe не были найдены. FFmpeg необходим для воспроизведения мультимедиа и превью.",
|
||||
"ffmpeg.missing.status": "{ffmpeg}: {ffmpeg_status}<br>{ffprobe}: {ffprobe_status}",
|
||||
"field.copy": "Копировать поле",
|
||||
@@ -161,9 +156,6 @@
|
||||
"json_migration.title.new_lib": "<h2>Библиотека версии 9.5+</h2>",
|
||||
"json_migration.title.old_lib": "<h2>Библиотека версии 9.4</h2>",
|
||||
"landing.open_create_library": "Открыть/создать библиотеку {shortcut}",
|
||||
"library_info.stats.entries": "Записи:",
|
||||
"library_info.stats.fields": "Поля:",
|
||||
"library_info.stats.tags": "Теги:",
|
||||
"library.field.add": "Добавить поле",
|
||||
"library.field.confirm_remove": "Вы уверены, что хотите удалить поле \"{name}\"?",
|
||||
"library.field.mixed_data": "Смешанные данные",
|
||||
@@ -175,6 +167,9 @@
|
||||
"library.refresh.scanning_preparing": "Сканирование папок на наличие новых файлов...\nПодготовка...",
|
||||
"library.refresh.title": "Обновление папок",
|
||||
"library.scan_library.title": "Сканирование библиотеки",
|
||||
"library_info.stats.entries": "Записи:",
|
||||
"library_info.stats.fields": "Поля:",
|
||||
"library_info.stats.tags": "Теги:",
|
||||
"library_object.name": "Название",
|
||||
"library_object.name_required": "Название (Обязательно)",
|
||||
"library_object.slug": "Уникальный ID",
|
||||
|
||||
@@ -26,13 +26,9 @@
|
||||
"entries.mirror.title": "Speglar Poster",
|
||||
"entries.mirror.window_title": "Spegla Poster",
|
||||
"entries.tags": "Etiketter",
|
||||
"entries.unlinked.delete": "Radera olänkade poster",
|
||||
"entries.unlinked.delete.confirm": "Är du säker att du vill radera följande {count} poster?",
|
||||
"entries.unlinked.delete.deleting": "Raderar poster",
|
||||
"entries.unlinked.delete.deleting_count": "Raderar {idx}/{count} olänkade poster",
|
||||
"entries.unlinked.delete_alt": "Radera olänkade poster",
|
||||
"entries.remove.plural.confirm": "Är du säker att du vill radera följande {count} poster?",
|
||||
"entries.generic.remove.removing": "Raderar poster",
|
||||
"entries.unlinked.description": "Varje post i biblioteket är länkad till en fil i en av dina kataloger. Om en fil länkad till en post är flyttad eller borttagen utanför TagStudio blir den olänkad. Olänkade poster kan automatiskt bli omlänkade genom att söka genom dina kataloger, manuellt omlänkade av användaren eller tas bort om så önskas.",
|
||||
"entries.unlinked.refresh_all": "Uppdatera alla",
|
||||
"entries.unlinked.relink.manual": "Länka om manuellt",
|
||||
"entries.unlinked.relink.title": "Länkar om poster",
|
||||
"entries.unlinked.scanning": "Skannar bibliotek efter olänkade poster...",
|
||||
|
||||
@@ -40,29 +40,24 @@
|
||||
"entries.duplicate.merge.label": "நகல் உள்ளீடுகளை ஒன்றிணைத்தல் ...",
|
||||
"entries.duplicate.refresh": "நகல் உள்ளீடுகளைப் புதுப்பி",
|
||||
"entries.duplicates.description": "மறுநுழைவுகள் என்பது, ஒரே கோப்பை குறிக்கும் பல நுழைவுகளை குறிக்கும். இவற்றை இணைப்பதால், அனைத்து மறுநுழைவுகளின் குறிச்சொற்களும் மெட்டாடேட்டாவும் ஒரே ஒட்டுமொத்த நுழைவாகச் சேர்க்கப்படும். இவற்றை 'மறுகோப்புகள்' என்பதுடன் குழப்பக் கூடாது, ஏனெனில் அவை டாக் ஸ்டுடியோவுக்கு வெளியேயுள்ள கோப்புகளின் நகல்களாகும்.",
|
||||
"entries.generic.remove.removing": "உள்ளீடுகள் நீக்கப்படுகிறது",
|
||||
"entries.mirror": "& கண்ணாடி",
|
||||
"entries.mirror.confirmation": "பின்வரும் உள்ளீடுகளைப் பிரதிபலிக்க விரும்புகிறீர்களா {count}?",
|
||||
"entries.mirror.label": "{idx}/{total} உள்ளீடுகளைப் பிரதிபலிக்கப்படுகின்றது...",
|
||||
"entries.mirror.title": "உள்ளீடுகள் பிரதிபழிக்கப்படுகின்றது",
|
||||
"entries.mirror.window_title": "கண்ணாடி உள்ளீடுகள்",
|
||||
"entries.remove.plural.confirm": "பின்வரும் உள்ளீடுகளை நிச்சயமாக நீக்க விரும்புகிறீர்களா {count}?",
|
||||
"entries.running.dialog.new_entries": "{total} புதிய கோப்பு உள்ளீடுகளைச் சேர்ப்பது ...",
|
||||
"entries.running.dialog.title": "புதிய கோப்பு உள்ளீடுகளைச் சேர்ப்பது",
|
||||
"entries.tags": "குறிச்சொற்கள்",
|
||||
"entries.unlinked.delete": "இணைக்கப்படாத உள்ளீடுகளை நீக்கு",
|
||||
"entries.unlinked.delete.confirm": "பின்வரும் உள்ளீடுகளை நிச்சயமாக நீக்க விரும்புகிறீர்களா {count}?",
|
||||
"entries.unlinked.delete.deleting": "உள்ளீடுகள் நீக்கப்படுகிறது",
|
||||
"entries.unlinked.delete.deleting_count": "{idx}/{count} இணைக்கப்படாத உள்ளீடுகள் நீக்கப்படுகிறது",
|
||||
"entries.unlinked.delete_alt": "டி & லெட் இணைக்கப்படாத உள்ளீடுகள்",
|
||||
"entries.unlinked.description": "ஒவ்வொரு நூலக நுழைவும் உங்கள் கோப்பகங்களில் ஒன்றில் ஒரு கோப்போடு இணைக்கப்பட்டுள்ளது. ஒரு நுழைவுடன் இணைக்கப்பட்ட ஒரு கோப்பு டாக்ச்டுடியோவுக்கு வெளியே நகர்த்தப்பட்டால் அல்லது நீக்கப்பட்டால், அது பின்னர் இணைக்கப்படாததாகக் கருதப்படுகிறது.",
|
||||
"entries.unlinked.missing_count.none": "இணைக்கப்படாத உள்ளீடுகள்: இதற்கில்லை",
|
||||
"entries.unlinked.missing_count.some": "இணைக்கப்படாத உள்ளீடுகள்: {count}",
|
||||
"entries.unlinked.refresh_all": "& அனைத்தையும் புதுப்பிக்கவும்",
|
||||
"entries.unlinked.relink.attempting": "{idx}/{missing_count} உள்ளீடுகளை மீண்டும் இணைக்க முயற்சிக்கிறது, {fixed_count} மீண்டும் இணைக்கப்பட்டது",
|
||||
"entries.unlinked.relink.attempting": "{index}/{unlinked_count} உள்ளீடுகளை மீண்டும் இணைக்க முயற்சிக்கிறது, {fixed_count} மீண்டும் இணைக்கப்பட்டது",
|
||||
"entries.unlinked.relink.manual": "& கையேடு மறுபரிசீலனை",
|
||||
"entries.unlinked.relink.title": "உள்ளீடுகள் மீண்டும் இணைக்கப்படுகின்றது",
|
||||
"entries.unlinked.scanning": "இணைக்கப்படாத நுழைவுகளை புத்தககல்லரியில் சோதனை செய்யப்படுகிறது...",
|
||||
"entries.unlinked.search_and_relink": "& தேடல் && relink",
|
||||
"entries.unlinked.title": "இணைக்கப்படாத உள்ளீடுகளைச் சரிசெய்யவும்",
|
||||
"entries.unlinked.unlinked_count": "இணைக்கப்படாத உள்ளீடுகள்: {count}",
|
||||
"ffmpeg.missing.description": "FFMPEG மற்றும்/அல்லது FFPROBE கண்டுபிடிக்கப்படவில்லை. மல்டிமீடியா பிளேபேக் மற்றும் சிறுபடங்களுக்கு FFMPEG தேவைப்படுகிறது.",
|
||||
"ffmpeg.missing.status": "{ffmpeg}: {ffmpeg_status} <br> {ffprobe}: {ffprobe_status}",
|
||||
"field.copy": "நகல் புலம்",
|
||||
@@ -161,9 +156,6 @@
|
||||
"json_migration.title.new_lib": "<h2> V9.5+ நூலகம் </h2>",
|
||||
"json_migration.title.old_lib": "<h2> V9.4 நூலகம் </h2>",
|
||||
"landing.open_create_library": "நூலகத்தைத் திறக்கவும்/உருவாக்கவும் {shortcut}",
|
||||
"library_info.stats.entries": "உள்ளீடுகள்:",
|
||||
"library_info.stats.fields": "புலங்கள்:",
|
||||
"library_info.stats.tags": "குறிச்சொற்கள்:",
|
||||
"library.field.add": "புலத்தைச் சேர்க்க",
|
||||
"library.field.confirm_remove": "இந்த \"{name}\" புலத்தை நிச்சயமாக அகற்ற விரும்புகிறீர்களா?",
|
||||
"library.field.mixed_data": "கலப்பு தரவு",
|
||||
@@ -175,6 +167,9 @@
|
||||
"library.refresh.scanning_preparing": "புதிய கோப்புகளுக்கான அடைவுகள் சோதனை செய்யப்படுகின்றது...\nதயாராகிறது...",
|
||||
"library.refresh.title": "கோப்பகங்கள் புதுப்பிக்கப்படுகின்றன",
|
||||
"library.scan_library.title": "புத்தககல்லரி சோதனை செய்யப்படுகிறது",
|
||||
"library_info.stats.entries": "உள்ளீடுகள்:",
|
||||
"library_info.stats.fields": "புலங்கள்:",
|
||||
"library_info.stats.tags": "குறிச்சொற்கள்:",
|
||||
"library_object.name": "பெயர்",
|
||||
"library_object.name_required": "பெயர் (தேவை)",
|
||||
"library_object.slug": "ஐடி ச்லக்",
|
||||
|
||||
@@ -40,29 +40,24 @@
|
||||
"entries.duplicate.merge.label": "mi wan e ijo sama...",
|
||||
"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.",
|
||||
"entries.generic.remove.removing": "mi weka e ijo",
|
||||
"entries.mirror": "jasi&ma",
|
||||
"entries.mirror.confirmation": "mi jasima e ijo {count}. ni li pona anu seme?",
|
||||
"entries.mirror.label": "mi jasima e ijo {idx}/{total}...",
|
||||
"entries.mirror.title": "mi jasima e ijo",
|
||||
"entries.mirror.window_title": "o jasima e ijo",
|
||||
"entries.remove.plural.confirm": "mi weka e ijo {count}. ni li pona anu seme?",
|
||||
"entries.running.dialog.new_entries": "mi pana e lipu sin {total}...",
|
||||
"entries.running.dialog.title": "mi pana e lipu sin",
|
||||
"entries.tags": "poki",
|
||||
"entries.unlinked.delete": "o weka e ijo pi ijo lon ala",
|
||||
"entries.unlinked.delete.confirm": "mi weka e ijo {count}. ni li pona anu seme?",
|
||||
"entries.unlinked.delete.deleting": "mi weka e ijo",
|
||||
"entries.unlinked.delete.deleting_count": "mi weka e ijo {idx}/{count} pi ijo lon ala",
|
||||
"entries.unlinked.delete_alt": "o weka e ijo pi &linja ala",
|
||||
"entries.unlinked.description": "ijo ale li jo e ijo lon tomo sina. ona li tawa anu weka lon ilo TagStudio ala la, ona li jo ala e ijo lon.<br><br>ijo pi ijo lon li ken alasa lon tomo li ken kama jo e ijo lon. ante la sina ken weka e ona.",
|
||||
"entries.unlinked.missing_count.none": "ijo pi ijo lon ala: N/A",
|
||||
"entries.unlinked.missing_count.some": "ijo pi ijo lon ala: {count}",
|
||||
"entries.unlinked.refresh_all": "o lukin sin e ale (&R)",
|
||||
"entries.unlinked.relink.attempting": "mi o pana e ijo lon tawa ijo {idx}/{missing_count}. mi pana e ijo lon tawa ijo {fixed_count}",
|
||||
"entries.unlinked.relink.attempting": "mi o pana e ijo lon tawa ijo {index}/{unlinked_count}. mi pana e ijo lon tawa ijo {fixed_count}",
|
||||
"entries.unlinked.relink.manual": "sina o pana e ijo lon tawa ijo (&M)",
|
||||
"entries.unlinked.relink.title": "mi pana e ijo lon tawa ijo",
|
||||
"entries.unlinked.scanning": "mi o alasa e ijo pi ijo lon ala...",
|
||||
"entries.unlinked.search_and_relink": "o ala&sa o pana e ijo lon tawa ijo",
|
||||
"entries.unlinked.title": "o pona e ijo pi ijo lon ala",
|
||||
"entries.unlinked.unlinked_count": "ijo pi ijo lon ala: {count}",
|
||||
"ffmpeg.missing.description": "mi lukin ala e ilo FFmpeg e/anu ilo FFprobe. sina wile e kepeken sin pi musi mute e sitelen lili pi musi mute la sina wile e ilo FFmpeg.",
|
||||
"ffmpeg.missing.status": "{ffmpeg}: {ffmpeg_status}<br>{ffprobe}: {ffprobe_status}",
|
||||
"field.copy": "o kama jo e ma sama",
|
||||
@@ -155,9 +150,6 @@
|
||||
"json_migration.title.new_lib": "<h2>tomo pi ilo nanpa 9.5+</h2>",
|
||||
"json_migration.title.old_lib": "<h2>tomo pi ilo nanpa 9.4</h2>",
|
||||
"landing.open_create_library": "o open anu pali sin e tomo {shortcut}",
|
||||
"library_info.stats.entries": "ijo:",
|
||||
"library_info.stats.fields": "ma:",
|
||||
"library_info.stats.tags": "poki:",
|
||||
"library.field.add": "o pana e ma",
|
||||
"library.field.confirm_remove": "sina wile ala wile weka e ma \"{name}\" ni?",
|
||||
"library.field.mixed_data": "sona nasa",
|
||||
@@ -169,6 +161,9 @@
|
||||
"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",
|
||||
"library_info.stats.entries": "ijo:",
|
||||
"library_info.stats.fields": "ma:",
|
||||
"library_info.stats.tags": "poki:",
|
||||
"library_object.name": "nimi",
|
||||
"library_object.name_required": "nimi (wile mute)",
|
||||
"library_object.slug": "ID Slug",
|
||||
|
||||
@@ -39,29 +39,24 @@
|
||||
"entries.duplicate.merge.label": "Yinelenen Kayıtlar Birleştiriliyor...",
|
||||
"entries.duplicate.refresh": "Yinelenen Kayıtları Yenile",
|
||||
"entries.duplicates.description": "Yinelenen kayıtlar, diskinizde aynı dosyaya işaret eden birden fazla kayıt olarak tanımlanmaktadır. Bu kayıtları birleştirdiğinizde, yinelenen tüm kayıtların içerisindeki etiketler ve metadata bilgisi tek bir tane kayıt üzerinde birleştirilecektir. Bu, \"yinelenen dosyalar\" ile karıştırılmamalıdır. Yinelenen dosyalar, TagStudio'nun dışında birden fazla kere bulunan dosyalarınızdır.",
|
||||
"entries.generic.remove.removing": "Kayıtlar Siliniyor",
|
||||
"entries.mirror": "&Yansıt",
|
||||
"entries.mirror.confirmation": "{count} kaydı yansıtmak istediğinden emin misin?",
|
||||
"entries.mirror.label": "{idx}/{total} Kayıt Yansıtılıyor...",
|
||||
"entries.mirror.title": "Kayıtlar Yansıtılıyor",
|
||||
"entries.mirror.window_title": "Kayıtları Yansıt",
|
||||
"entries.remove.plural.confirm": "{count} tane kayıtları silmek istediğinden emin misin?",
|
||||
"entries.running.dialog.new_entries": "{total} Yeni Dosya Kaydı Ekleniyor...",
|
||||
"entries.running.dialog.title": "Yeni Dosya Kayıtları Ekleniyor",
|
||||
"entries.tags": "Etiketler",
|
||||
"entries.unlinked.delete": "Kopmuş Kayıtları Sil",
|
||||
"entries.unlinked.delete.confirm": "{count} tane kayıtları silmek istediğinden emin misin?",
|
||||
"entries.unlinked.delete.deleting": "Kayıtlar Siliniyor",
|
||||
"entries.unlinked.delete.deleting_count": "{idx}/{count} Kopmuş Kayıt Siliniyor",
|
||||
"entries.unlinked.delete_alt": "Kopmuş Kayıtları S&il",
|
||||
"entries.unlinked.description": "Kütüphanenizdeki her bir kayıt, dizinlerinizden bir tane dosya ile eşleştirilmektedir. Eğer bir kayıta bağlı dosya TagStudio dışında taşınır veya silinirse, o dosya artık kopmuş olarak sayılır.<br><br>Kopmuş kayıtlar dizinlerinizde arama yapılırken otomatik olarak tekrar eşleştirilebilir, manuel olarak sizin tarafınızdan eşleştirilebilir veya isteğiniz üzere silinebilir.",
|
||||
"entries.unlinked.missing_count.none": "Kopmuş Kayıtlar: Yok",
|
||||
"entries.unlinked.missing_count.some": "Kopmuş Kayıtlar: {count}",
|
||||
"entries.unlinked.refresh_all": "&Tümünü Yenile",
|
||||
"entries.unlinked.relink.attempting": "{idx}/{missing_count} Kayıt Yeniden Eşleştirilmeye Çalışılıyor, {fixed_count} Başarıyla Yeniden Eşleştirildi",
|
||||
"entries.unlinked.relink.attempting": "{index}/{unlinked_count} Kayıt Yeniden Eşleştirilmeye Çalışılıyor, {fixed_count} Başarıyla Yeniden Eşleştirildi",
|
||||
"entries.unlinked.relink.manual": "&Manuel Yeniden Eşleştirme",
|
||||
"entries.unlinked.relink.title": "Kayıtlar Yeniden Eşleştiriliyor",
|
||||
"entries.unlinked.scanning": "Kütüphane, Kopmuş Kayıtlar için Taranıyor...",
|
||||
"entries.unlinked.search_and_relink": "&Ara && Yeniden Eşleştir",
|
||||
"entries.unlinked.title": "Kopmuş Kayıtları Düzelt",
|
||||
"entries.unlinked.unlinked_count": "Kopmuş Kayıtlar: {count}",
|
||||
"field.copy": "Ek Bilgiyi Kopyala",
|
||||
"field.edit": "Ek Bilgiyi Düzenle",
|
||||
"field.paste": "Ek Bilgiyi Yapıştır",
|
||||
@@ -157,9 +152,6 @@
|
||||
"json_migration.title.new_lib": "<h2>v9.5+ Kütüphane</h2>",
|
||||
"json_migration.title.old_lib": "<h2>v9.4 Kütüphane</h2>",
|
||||
"landing.open_create_library": "Kütüphane Aç/Oluştur {shortcut}",
|
||||
"library_info.stats.entries": "Kayıtlar:",
|
||||
"library_info.stats.fields": "Ek Bilgiler:",
|
||||
"library_info.stats.tags": "Etiketler:",
|
||||
"library.field.add": "Ek Bilgi Ekle",
|
||||
"library.field.confirm_remove": "Bu \"{name}\" ek bilgisini silmek istediğinden emin misin?",
|
||||
"library.field.mixed_data": "Karışık Veri",
|
||||
@@ -171,6 +163,9 @@
|
||||
"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",
|
||||
"library_info.stats.entries": "Kayıtlar:",
|
||||
"library_info.stats.fields": "Ek Bilgiler:",
|
||||
"library_info.stats.tags": "Etiketler:",
|
||||
"library_object.name": "Ad",
|
||||
"library_object.name_required": "Ad (Gerekli)",
|
||||
"library_object.slug": "Kimlik Kodu",
|
||||
|
||||
@@ -40,29 +40,24 @@
|
||||
"entries.duplicate.merge.label": "正在合并重复项目...",
|
||||
"entries.duplicate.refresh": "重新整理重复项目",
|
||||
"entries.duplicates.description": "重复项目被定义为多个指向磁盘上同一文件的项目。合并这些项目将把所有重复项目的标签和元数据整合为一个统一的项目。这与“重复文件”不同,后者是指在 TagStudio 之外的文件本身的重复。",
|
||||
"entries.generic.remove.removing": "正在删除项目",
|
||||
"entries.mirror": "镜像(&m)",
|
||||
"entries.mirror.confirmation": "您确定要镜像以下 {count} 条项目吗?",
|
||||
"entries.mirror.label": "正在镜像 {idx}/{total} 个项目...",
|
||||
"entries.mirror.title": "进行项目镜像",
|
||||
"entries.mirror.window_title": "项目镜像",
|
||||
"entries.remove.plural.confirm": "您确定要删除以下 {count} 个项目?",
|
||||
"entries.running.dialog.new_entries": "正在加入 {total} 个新文件项目...",
|
||||
"entries.running.dialog.title": "正在加入新文件项目",
|
||||
"entries.tags": "标签",
|
||||
"entries.unlinked.delete": "删除未链接的项目",
|
||||
"entries.unlinked.delete.confirm": "您确定要删除以下 {count} 个项目?",
|
||||
"entries.unlinked.delete.deleting": "正在删除项目",
|
||||
"entries.unlinked.delete.deleting_count": "正在删除 {idx}/{count} 个未链接的项目",
|
||||
"entries.unlinked.delete_alt": "删除未链接的项目(&l)",
|
||||
"entries.unlinked.description": "每个仓库条目都链接到一个目录中的文件。如果链接到某个条目的文件在TagStudio之外被移动或删除,则会被视为未链接。<br><br>未链接的条目可能会通过搜索目录自动重新链接,或者根据需要删除。",
|
||||
"entries.unlinked.missing_count.none": "未链接的项目: 无",
|
||||
"entries.unlinked.missing_count.some": "未链接的项目: {count}",
|
||||
"entries.unlinked.refresh_all": "全部刷新(&r)",
|
||||
"entries.unlinked.relink.attempting": "正在尝试重新链接 {idx}/{missing_count} 个项目, {fixed_count} 个项目成功重链",
|
||||
"entries.unlinked.relink.attempting": "正在尝试重新链接 {index}/{unlinked_count} 个项目, {fixed_count} 个项目成功重链",
|
||||
"entries.unlinked.relink.manual": "手动重新链接(&m)",
|
||||
"entries.unlinked.relink.title": "正在重新链接项目",
|
||||
"entries.unlinked.scanning": "正在扫描仓库以寻找未链接的项目...",
|
||||
"entries.unlinked.search_and_relink": "搜索并重新链接(&s)",
|
||||
"entries.unlinked.title": "修复未链接的项目",
|
||||
"entries.unlinked.unlinked_count": "未链接的项目: {count}",
|
||||
"ffmpeg.missing.description": "找不到 FFmpeg 或 FFprobe。多媒体播放和缩略图生成需要 FFmpeg 支持。",
|
||||
"ffmpeg.missing.status": "{ffmpeg}: {ffmpeg_status}<br>{ffprobe}: {ffprobe_status}",
|
||||
"field.copy": "复制字段",
|
||||
@@ -161,9 +156,6 @@
|
||||
"json_migration.title.new_lib": "<h2>v9.5+ 仓库</h2>",
|
||||
"json_migration.title.old_lib": "<h2>v9.4 仓库</h2>",
|
||||
"landing.open_create_library": "打开/创建仓库 {shortcut}",
|
||||
"library_info.stats.entries": "项目:",
|
||||
"library_info.stats.fields": "字段:",
|
||||
"library_info.stats.tags": "标签:",
|
||||
"library.field.add": "新增字段",
|
||||
"library.field.confirm_remove": "您确定要移除此 \"{name}\" 字段?",
|
||||
"library.field.mixed_data": "混合数据",
|
||||
@@ -175,6 +167,9 @@
|
||||
"library.refresh.scanning_preparing": "正在扫描文件夹中的新文件...\n准备中...",
|
||||
"library.refresh.title": "正在刷新目录",
|
||||
"library.scan_library.title": "正在扫描仓库",
|
||||
"library_info.stats.entries": "项目:",
|
||||
"library_info.stats.fields": "字段:",
|
||||
"library_info.stats.tags": "标签:",
|
||||
"library_object.name": "仓库名",
|
||||
"library_object.name_required": "仓库名(必填)",
|
||||
"library_object.slug": "ID 短链",
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
"app.git": "Git 提交更新",
|
||||
"app.pre_release": "預先發布版本",
|
||||
"app.title": "{base_title} - 文件庫「{library_dir}」",
|
||||
"color_manager.title": "管理標籤顏色",
|
||||
"color.color_border": "在邊框使用第二個顏色",
|
||||
"color.confirm_delete": "您確定要刪除「{color_name}」嗎?",
|
||||
"color.delete": "刪除顏色",
|
||||
@@ -19,10 +18,11 @@
|
||||
"color.namespace.delete.title": "刪除顏色命名空間",
|
||||
"color.new": "新增顏色",
|
||||
"color.placeholder": "顏色",
|
||||
"color.primary_required": "主要顏色 (必填)",
|
||||
"color.primary": "主要顏色",
|
||||
"color.primary_required": "主要顏色 (必填)",
|
||||
"color.secondary": "次要顏色",
|
||||
"color.title.no_color": "無顏色",
|
||||
"color_manager.title": "管理標籤顏色",
|
||||
"dependency.missing.title": "未找到 {dependency}",
|
||||
"drop_import.description": "以下檔案與文件庫中已存在的檔案路徑重複",
|
||||
"drop_import.duplicates_choice.plural": "以下 {count} 個檔案與文件庫中已存在的檔案路徑重複",
|
||||
@@ -36,33 +36,28 @@
|
||||
"edit.copy_fields": "複製欄位",
|
||||
"edit.paste_fields": "貼上欄位",
|
||||
"edit.tag_manager": "管理標籤",
|
||||
"entries.duplicate.merge.label": "正在合併重複項目...",
|
||||
"entries.duplicate.merge": "合併重複項目",
|
||||
"entries.duplicate.merge.label": "正在合併重複項目...",
|
||||
"entries.duplicate.refresh": "重新整理重複項目",
|
||||
"entries.duplicates.description": "重複項目的定義為多個項目指向硬碟中的同一個檔案。合併這些重複項目會將其所有的標籤和元資料合併為一個單獨的項目。這些並不是重複的檔案,重複的檔案是 TagStudio 以外的重複檔案。",
|
||||
"entries.generic.remove.removing": "正在刪除項目",
|
||||
"entries.mirror": "鏡像 (&M)",
|
||||
"entries.mirror.confirmation": "您確定要鏡像 {count} 個項目嗎?",
|
||||
"entries.mirror.label": "正在鏡像 {idx}/{total} 個項目...",
|
||||
"entries.mirror.title": "鏡像項目",
|
||||
"entries.mirror.window_title": "鏡像項目",
|
||||
"entries.mirror": "鏡像 (&M)",
|
||||
"entries.remove.plural.confirm": "您確定要刪除 {count} 個項目嗎?",
|
||||
"entries.running.dialog.new_entries": "正在加入 {total} 個新檔案項目...",
|
||||
"entries.running.dialog.title": "正在加入新檔案項目",
|
||||
"entries.tags": "標籤",
|
||||
"entries.unlinked.delete_alt": "刪除未連接項目",
|
||||
"entries.unlinked.delete.confirm": "您確定要刪除 {count} 個項目嗎?",
|
||||
"entries.unlinked.delete.deleting_count": "正在刪除 {idx}/{count} 個未連接項目",
|
||||
"entries.unlinked.delete.deleting": "正在刪除項目",
|
||||
"entries.unlinked.delete": "刪除未連接項目",
|
||||
"entries.unlinked.description": "每個文件庫的項目都連接到您的其中一個檔案,如果一個已連接的檔案被刪除或移出 TagStudio,那麼這個項目會被歸類為「未連接」。<br><br>您可以透過搜尋您的檔案來讓未連接的項目自動重新連接,或者自動刪除這些未連接項目。",
|
||||
"entries.unlinked.missing_count.none": "未連接項目:無",
|
||||
"entries.unlinked.missing_count.some": "未連接項目:{count}",
|
||||
"entries.unlinked.refresh_all": "重新整理全部 (&R)",
|
||||
"entries.unlinked.relink.attempting": "正在嘗試重新連接 {idx}/{missing_count} 個項目,已成功重新連接 {fixed_count} 個",
|
||||
"entries.unlinked.relink.attempting": "正在嘗試重新連接 {index}/{unlinked_count} 個項目,已成功重新連接 {fixed_count} 個",
|
||||
"entries.unlinked.relink.manual": "手動重新連接 (&M)",
|
||||
"entries.unlinked.relink.title": "正在重新連接",
|
||||
"entries.unlinked.scanning": "正在掃描文件庫中的未連接項目...",
|
||||
"entries.unlinked.search_and_relink": "搜尋並重新連接 (&S)",
|
||||
"entries.unlinked.title": "修復未連接項目",
|
||||
"entries.unlinked.unlinked_count": "未連接項目:{count}",
|
||||
"ffmpeg.missing.description": "未找到「FFmpeg」和/或「FFprobe」。必須安裝「FFmpeg」才能進行多媒體播放和縮圖產生。",
|
||||
"ffmpeg.missing.status": "{ffmpeg}: {ffmpeg_status}<br>{ffprobe}: {ffprobe_status}",
|
||||
"field.copy": "複製欄位",
|
||||
@@ -71,7 +66,6 @@
|
||||
"file.date_added": "新增日期",
|
||||
"file.date_created": "建立日期",
|
||||
"file.date_modified": "修改日期",
|
||||
"file.path": "檔案路徑",
|
||||
"file.dimensions": "尺寸",
|
||||
"file.duplicates.description": "TagStudio 支援匯入 DupeGuru 結果來管理重複的檔案",
|
||||
"file.duplicates.dupeguru.advice": "在鏡像之後,您可以使用 DupeGuru 來刪除不需要的檔案。之後,利用 TagStudio 的「修復未連接項目」功能來刪除未連接項目。",
|
||||
@@ -80,67 +74,68 @@
|
||||
"file.duplicates.dupeguru.no_file": "沒有選擇 DupeGuru 檔案",
|
||||
"file.duplicates.dupeguru.open_file": "開啟 DupeGuru 結果檔案",
|
||||
"file.duplicates.fix": "修復重複的檔案",
|
||||
"file.duplicates.matches_uninitialized": "重複檔案:無",
|
||||
"file.duplicates.matches": "重複檔案:{count}",
|
||||
"file.duplicates.mirror_entries": "鏡像項目 (&M)",
|
||||
"file.duplicates.matches_uninitialized": "重複檔案:無",
|
||||
"file.duplicates.mirror.description": "鏡像每個重複配對集的項目資料,合併所有資料,同時不移除或重複欄位。此操作不會刪除任何檔案或資料。",
|
||||
"file.duplicates.mirror_entries": "鏡像項目 (&M)",
|
||||
"file.duration": "長度",
|
||||
"file.not_found": "未法找到檔案",
|
||||
"file.open_file_with": "使用指定程式開啟",
|
||||
"file.open_file": "開啟檔案",
|
||||
"file.open_file_with": "使用指定程式開啟",
|
||||
"file.open_location.generic": "在檔案管理員中顯示",
|
||||
"file.open_location.mac": "在 Finder 中顯示",
|
||||
"file.open_location.windows": "在檔案總管中顯示",
|
||||
"file.path": "檔案路徑",
|
||||
"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_alt": "套用 (&A)",
|
||||
"generic.apply": "套用",
|
||||
"generic.cancel_alt": "取消 (&C)",
|
||||
"generic.apply_alt": "套用 (&A)",
|
||||
"generic.cancel": "取消",
|
||||
"generic.cancel_alt": "取消 (&C)",
|
||||
"generic.close": "關閉",
|
||||
"generic.continue": "繼續",
|
||||
"generic.copy": "複製",
|
||||
"generic.cut": "剪下",
|
||||
"generic.delete_alt": "刪除 (&D)",
|
||||
"generic.delete": "刪除",
|
||||
"generic.done_alt": "完成 (&D)",
|
||||
"generic.delete_alt": "刪除 (&D)",
|
||||
"generic.done": "完成",
|
||||
"generic.edit_alt": "編輯 (&E)",
|
||||
"generic.done_alt": "完成 (&D)",
|
||||
"generic.edit": "編輯",
|
||||
"generic.edit_alt": "編輯 (&E)",
|
||||
"generic.filename": "檔案名稱",
|
||||
"generic.missing": "遺失",
|
||||
"generic.navigation.back": "返回",
|
||||
"generic.navigation.next": "下一個",
|
||||
"generic.none": "無",
|
||||
"generic.overwrite_alt": "覆寫 (&O)",
|
||||
"generic.overwrite": "覆寫",
|
||||
"generic.overwrite_alt": "覆寫 (&O)",
|
||||
"generic.paste": "貼上",
|
||||
"generic.recent_libraries": "最近使用的文件庫",
|
||||
"generic.rename_alt": "重新命名 (&R)",
|
||||
"generic.rename": "重新命名",
|
||||
"generic.rename_alt": "重新命名 (&R)",
|
||||
"generic.reset": "重設",
|
||||
"generic.save": "儲存",
|
||||
"generic.skip_alt": "跳過 (&S)",
|
||||
"generic.skip": "跳過",
|
||||
"generic.skip_alt": "跳過 (&S)",
|
||||
"home.search": "搜尋",
|
||||
"home.search_entries": "搜尋項目",
|
||||
"home.search_library": "搜尋文件庫",
|
||||
"home.search_tags": "搜尋標籤",
|
||||
"home.search": "搜尋",
|
||||
"home.thumbnail_size": "縮圖大小",
|
||||
"home.thumbnail_size.extra_large": "特大縮圖",
|
||||
"home.thumbnail_size.large": "大縮圖",
|
||||
"home.thumbnail_size.medium": "中縮圖",
|
||||
"home.thumbnail_size.mini": "迷你縮圖",
|
||||
"home.thumbnail_size.small": "小縮圖",
|
||||
"home.thumbnail_size": "縮圖大小",
|
||||
"json_migration.checking_for_parity": "正在檢查一致性...",
|
||||
"json_migration.creating_database_tables": "正在建立資料庫表格...",
|
||||
"json_migration.description": "<br>開啟並預覽文件庫遷移過程。除非您按下「完成遷移」,否則被遷移的文件庫<i>不會</i>被使用。<br><br>文件庫資料應該是一致的或者要有個「已一致」標籤。不一致的資料會以紅色顯示並會有「<b>(!)</b>」標示在旁邊。<br><center><i>對於較大的文件庫,這個過程可能會花到幾分鐘以上。</i></center>",
|
||||
"json_migration.discrepancies_found.description": "原始和被遷移的文件庫格式出現差異。請檢查並決定是否要繼續遷移。",
|
||||
"json_migration.discrepancies_found": "找到文件庫差異",
|
||||
"json_migration.discrepancies_found.description": "原始和被遷移的文件庫格式出現差異。請檢查並決定是否要繼續遷移。",
|
||||
"json_migration.finish_migration": "完成遷移",
|
||||
"json_migration.heading.aliases": "別名:",
|
||||
"json_migration.heading.colors": "顏色:",
|
||||
@@ -154,31 +149,31 @@
|
||||
"json_migration.heading.shorthands": "簡寫:",
|
||||
"json_migration.info.description": "TagStudio 版本<b>9.4 以下</b>的文件庫要被轉換至<b>9.5 以上</b>版本的格式。<br><h2>請注意!</h2><ul><li>您現在的文件庫<b><i>不會被</i></b>刪除</li><li>您個人的檔案<b><i>不會被</i></b>刪除、移動或變更</li><li>新的 9.5 以上版本儲存格式不能在 9.5 版本以前的 TagStudio 開啟</li></ul><h3>變更內容:</h3><ul><li>「變遷欄位」被「標籤類別」取代。現在,標籤會被直接加入至檔案項目。然後在標籤編輯選單會根據有「是一個類別」標示的父標籤被自動分類至不同的類別。任何標籤可以被標示為一個類別,而在其之下的子標籤會自己分類至被標示為類別的父標籤底下。</li><li>標籤顏色有經過調整和擴大,有些顏色又被重新命名或合併,但所有的顏色仍會轉換為完全一致或相近的顏色</li></ul><ul>",
|
||||
"json_migration.migrating_files_entries": "正在遷移 {entries:,d} 個項目...",
|
||||
"json_migration.migration_complete_with_discrepancies": "遷移完畢,找到差異",
|
||||
"json_migration.migration_complete": "遷移完成!",
|
||||
"json_migration.migration_complete_with_discrepancies": "遷移完畢,找到差異",
|
||||
"json_migration.start_and_preview": "開啟並預覽",
|
||||
"json_migration.title": "保存格式遷移:「{path}」",
|
||||
"json_migration.title.new_lib": "<h2>9.5 版本以上文件庫</h2>",
|
||||
"json_migration.title.old_lib": "<h2>9.4 版本文件庫</h2>",
|
||||
"json_migration.title": "保存格式遷移:「{path}」",
|
||||
"landing.open_create_library": "開啟/建立文件庫 {shortcut}",
|
||||
"library_info.stats.entries": "項目:",
|
||||
"library_info.stats.fields": "欄位:",
|
||||
"library_info.stats.tags": "標籤:",
|
||||
"library_object.name_required": "名稱 (必填)",
|
||||
"library_object.name": "名稱",
|
||||
"library_object.slug_required": "ID Slug (必填)",
|
||||
"library_object.slug": "ID Slug",
|
||||
"library.field.add": "新增欄位",
|
||||
"library.field.confirm_remove": "您確定要刪除「{name}」欄位嗎?",
|
||||
"library.field.mixed_data": "混合資料",
|
||||
"library.field.remove": "刪除欄位",
|
||||
"library.missing": "文件庫路徑遺失",
|
||||
"library.name": "文件庫",
|
||||
"library.refresh.scanning_preparing": "正在掃描目錄尋找新檔案...\n準備中...",
|
||||
"library.refresh.scanning.plural": "正在掃描目錄尋找新檔案...\n已搜尋 {searched_count} 個檔案,找到 {found_count} 個新檔案",
|
||||
"library.refresh.scanning.singular": "正在掃描目錄尋找新檔案...\n已搜尋 {searched_count} 個檔案,找到 {found_count} 個新檔案",
|
||||
"library.refresh.scanning_preparing": "正在掃描目錄尋找新檔案...\n準備中...",
|
||||
"library.refresh.title": "重新整理目錄",
|
||||
"library.scan_library.title": "掃描文件庫",
|
||||
"library_info.stats.entries": "項目:",
|
||||
"library_info.stats.fields": "欄位:",
|
||||
"library_info.stats.tags": "標籤:",
|
||||
"library_object.name": "名稱",
|
||||
"library_object.name_required": "名稱 (必填)",
|
||||
"library_object.slug": "ID Slug",
|
||||
"library_object.slug_required": "ID Slug (必填)",
|
||||
"macros.running.dialog.new_entries": "正在對 {count}/{total} 個新檔案項目執行設定的巨集指令...",
|
||||
"macros.running.dialog.title": "正在對新項目執行巨集指令",
|
||||
"media_player.autoplay": "自動播放",
|
||||
@@ -186,10 +181,11 @@
|
||||
"menu.delete_selected_files_ambiguous": "移動檔案至「{trash_term}」",
|
||||
"menu.delete_selected_files_plural": "移動多個檔案至「{trash_term}」",
|
||||
"menu.delete_selected_files_singular": "移動檔案至「{trash_term}」",
|
||||
"menu.edit": "編輯",
|
||||
"menu.edit.ignore_files": "忽略檔案和資料夾",
|
||||
"menu.edit.manage_tags": "管理標籤",
|
||||
"menu.edit.new_tag": "新增標籤 (&N)",
|
||||
"menu.edit": "編輯",
|
||||
"menu.file": "檔案 (&F)",
|
||||
"menu.file.clear_recent_libraries": "清除最近使用的文件庫",
|
||||
"menu.file.close_library": "關閉文件庫 (&C)",
|
||||
"menu.file.missing_library.message": "未找到文件庫(路徑:{library})",
|
||||
@@ -201,20 +197,19 @@
|
||||
"menu.file.refresh_directories": "重新整理目錄 (&R)",
|
||||
"menu.file.save_backup": "儲存文件庫備份 (&S)",
|
||||
"menu.file.save_library": "儲存文件庫",
|
||||
"menu.file": "檔案 (&F)",
|
||||
"menu.help.about": "關於",
|
||||
"menu.help": "幫助 (&H)",
|
||||
"menu.macros.folders_to_tags": "資料夾轉標籤",
|
||||
"menu.help.about": "關於",
|
||||
"menu.macros": "巨集指令 (&M)",
|
||||
"menu.macros.folders_to_tags": "資料夾轉標籤",
|
||||
"menu.select": "選擇",
|
||||
"menu.settings": "設定...",
|
||||
"menu.tools": "工具 (&T)",
|
||||
"menu.tools.fix_duplicate_files": "修復重複檔案",
|
||||
"menu.tools.fix_unlinked_entries": "修復未連接項目",
|
||||
"menu.tools": "工具 (&T)",
|
||||
"menu.view": "檢視 (&V)",
|
||||
"menu.window": "視窗 (&W)",
|
||||
"namespace.create.description_color": "標籤顏色使用命名空間作為色彩群組。所有自訂顏色必須先被放入一個命名空間群組。",
|
||||
"namespace.create.description": "TagStudio 使用命名空間來區分成群的物件,如標籤或顏色,以便這些物件能被匯出或分享。以「tagstudio」開頭的命名空間是 TagStudio 內部使用的命名空間。",
|
||||
"namespace.create.description_color": "標籤顏色使用命名空間作為色彩群組。所有自訂顏色必須先被放入一個命名空間群組。",
|
||||
"namespace.create.title": "建立命名空間",
|
||||
"namespace.new.button": "新增命名空間",
|
||||
"namespace.new.prompt": "新增一個命名空間以新增自訂顏色",
|
||||
@@ -225,11 +220,16 @@
|
||||
"select.clear": "清除選取",
|
||||
"select.inverse": "反向選取",
|
||||
"settings.clear_thumb_cache.title": "清除縮圖快取",
|
||||
"settings.dateformat.english": "英文",
|
||||
"settings.dateformat.international": "國際",
|
||||
"settings.dateformat.label": "日期格式",
|
||||
"settings.dateformat.system": "系統",
|
||||
"settings.filepath.label": "檔案路徑可見性",
|
||||
"settings.filepath.option.full": "僅顯示絕對檔案路徑",
|
||||
"settings.filepath.option.name": "僅顯示檔案名稱",
|
||||
"settings.filepath.option.relative": "僅顯示相對檔案路徑",
|
||||
"settings.global": "全域設定",
|
||||
"settings.hourformat.label": "24 小時制",
|
||||
"settings.language": "語言",
|
||||
"settings.library": "文件庫設定",
|
||||
"settings.open_library_on_start": "啟動時開啟文件庫",
|
||||
@@ -237,21 +237,16 @@
|
||||
"settings.restart_required": "需要重新啟動 TagStudio 才能使變更生效",
|
||||
"settings.show_filenames_in_grid": "在網格中顯示檔案名稱",
|
||||
"settings.show_recent_libraries": "顯示最近使用的文件庫",
|
||||
"settings.tag_click_action.label": "標籤點選動作",
|
||||
"settings.tag_click_action.add_to_search": "加入標籤至搜尋範圍",
|
||||
"settings.tag_click_action.label": "標籤點選動作",
|
||||
"settings.tag_click_action.open_edit": "編輯標籤",
|
||||
"settings.tag_click_action.set_search": "搜尋標籤",
|
||||
"settings.theme.dark": "深色模式",
|
||||
"settings.theme.label": "主題:",
|
||||
"settings.theme.light": "淺色模式",
|
||||
"settings.theme.system": "系統主題",
|
||||
"settings.dateformat.label": "日期格式",
|
||||
"settings.dateformat.system": "系統",
|
||||
"settings.dateformat.english": "英文",
|
||||
"settings.dateformat.international": "國際",
|
||||
"settings.hourformat.label": "24 小時制",
|
||||
"settings.zeropadding.label": "日期補零",
|
||||
"settings.title": "設定",
|
||||
"settings.zeropadding.label": "日期補零",
|
||||
"sorting.direction.ascending": "升序",
|
||||
"sorting.direction.descending": "降序",
|
||||
"splash.opening_library": "正在開啟「{library_path}」...",
|
||||
@@ -269,33 +264,33 @@
|
||||
"status.library_version_expected": "預期版本:",
|
||||
"status.library_version_found": "找到版本:",
|
||||
"status.library_version_mismatch": "文件庫版本不符!",
|
||||
"status.results_found": "找到 {count} 個結果 ({time_span})",
|
||||
"status.results.invalid_syntax": "搜尋語法錯誤:",
|
||||
"status.results": "結果",
|
||||
"tag_manager.title": "文件庫標籤",
|
||||
"tag.add_to_search": "加入至搜尋範圍",
|
||||
"tag.add.plural": "新增標籤",
|
||||
"status.results.invalid_syntax": "搜尋語法錯誤:",
|
||||
"status.results_found": "找到 {count} 個結果 ({time_span})",
|
||||
"tag.add": "新增標籤",
|
||||
"tag.add.plural": "新增標籤",
|
||||
"tag.add_to_search": "加入至搜尋範圍",
|
||||
"tag.aliases": "別名",
|
||||
"tag.all_tags": "所有標籤",
|
||||
"tag.choose_color": "選擇標籤顏色",
|
||||
"tag.color": "標籤顏色",
|
||||
"tag.confirm_delete": "您確定要刪除「{tag_name}」嗎?",
|
||||
"tag.create_add": "建立並新增「{query}」",
|
||||
"tag.create": "建立標籤",
|
||||
"tag.create_add": "建立並新增「{query}」",
|
||||
"tag.disambiguation.tooltip": "使用此標籤消除歧義",
|
||||
"tag.edit": "編輯標籤",
|
||||
"tag.is_category": "是一個類別",
|
||||
"tag.name": "標籤名稱",
|
||||
"tag.new": "新增標籤",
|
||||
"tag.parent_tags": "父標籤",
|
||||
"tag.parent_tags.add": "新增父標籤",
|
||||
"tag.parent_tags.description": "在搜尋中,該可以被當作任何其他父標籤。",
|
||||
"tag.parent_tags": "父標籤",
|
||||
"tag.remove": "刪除標籤",
|
||||
"tag.search_for_tag": "搜尋標籤",
|
||||
"tag.shorthand": "簡寫",
|
||||
"tag.tag_name_required": "標籤名稱 (必填)",
|
||||
"tag.view_limit": "檢視限制:",
|
||||
"tag_manager.title": "文件庫標籤",
|
||||
"trash.context.ambiguous": "移動檔案至「{trash_term}」",
|
||||
"trash.context.plural": "移動多個檔案移至「{trash_term}」",
|
||||
"trash.context.singular": "移動檔案至「{trash_term}」",
|
||||
|
||||
@@ -9,8 +9,8 @@ import pytest
|
||||
|
||||
from tagstudio.core.library.alchemy.enums import BrowsingState
|
||||
from tagstudio.core.library.alchemy.library import Library
|
||||
from tagstudio.core.utils.missing_files import MissingRegistry
|
||||
from tagstudio.core.utils.types import unwrap
|
||||
from tagstudio.core.utils.unlinked_registry import UnlinkedRegistry
|
||||
|
||||
CWD = Path(__file__).parent
|
||||
|
||||
@@ -18,16 +18,16 @@ CWD = Path(__file__).parent
|
||||
# NOTE: Does this test actually work?
|
||||
@pytest.mark.parametrize("library", [TemporaryDirectory()], indirect=True)
|
||||
def test_refresh_missing_files(library: Library):
|
||||
registry = MissingRegistry(library=library)
|
||||
registry = UnlinkedRegistry(lib=library)
|
||||
|
||||
# touch the file `one/two/bar.md` but in wrong location to simulate a moved file
|
||||
(unwrap(library.library_dir) / "bar.md").touch()
|
||||
|
||||
# no files actually exist, so it should return all entries
|
||||
assert list(registry.refresh_missing_files()) == [0, 1]
|
||||
assert list(registry.refresh_unlinked_files()) == [0, 1]
|
||||
|
||||
# neither of the library entries exist
|
||||
assert len(registry.missing_file_entries) == 2
|
||||
assert len(registry.unlinked_entries) == 2
|
||||
|
||||
# iterate through two files
|
||||
assert list(registry.fix_unlinked_entries()) == [0, 1]
|
||||
|
||||
19
tests/qt/test_resource_manager.py
Normal file
19
tests/qt/test_resource_manager.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# Copyright (C) 2025
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
|
||||
import structlog
|
||||
|
||||
from tagstudio.core.utils.types import unwrap
|
||||
from tagstudio.qt.resource_manager import ResourceManager
|
||||
|
||||
logger = structlog.get_logger()
|
||||
|
||||
|
||||
def test_get():
|
||||
rm = ResourceManager()
|
||||
|
||||
for res in rm._map: # pyright: ignore[reportPrivateUsage]
|
||||
assert rm.get(res), f"Could not get resource '{res}'"
|
||||
assert unwrap(rm.get_path(res)).exists(), f"Filepath for resource '{res}' does not exist"
|
||||
@@ -9,6 +9,9 @@ from pathlib import Path
|
||||
import pytest
|
||||
|
||||
from tagstudio.core.constants import TS_FOLDER_NAME
|
||||
from tagstudio.core.library.alchemy.constants import (
|
||||
SQL_FILENAME,
|
||||
)
|
||||
from tagstudio.core.library.alchemy.library import Library
|
||||
|
||||
CWD = Path(__file__)
|
||||
@@ -36,8 +39,8 @@ def test_library_migrations(path: str):
|
||||
temp_path_ts = temp_path / TS_FOLDER_NAME
|
||||
temp_path_ts.mkdir(exist_ok=True)
|
||||
shutil.copy(
|
||||
original_path / TS_FOLDER_NAME / Library.SQL_FILENAME,
|
||||
temp_path / TS_FOLDER_NAME / Library.SQL_FILENAME,
|
||||
original_path / TS_FOLDER_NAME / SQL_FILENAME,
|
||||
temp_path / TS_FOLDER_NAME / SQL_FILENAME,
|
||||
)
|
||||
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user