Compare commits

...

8 Commits

Author SHA1 Message Date
Hosted Weblate
dd13273cdd Translated using Weblate (Toki Pona)
Currently translated at 96.1% (348 of 362 strings)

Translated using Weblate (Toki Pona)

Currently translated at 96.1% (348 of 362 strings)

Translated using Weblate (Toki Pona)

Currently translated at 96.1% (348 of 362 strings)

Translated using Weblate (Toki Pona)

Currently translated at 96.1% (348 of 362 strings)

Translated using Weblate (Toki Pona)

Currently translated at 96.1% (348 of 362 strings)

Translated using Weblate (Toki Pona)

Currently translated at 95.0% (344 of 362 strings)

Translated using Weblate (Toki Pona)

Currently translated at 95.0% (344 of 362 strings)

Translated using Weblate (Toki Pona)

Currently translated at 95.0% (344 of 362 strings)

Translated using Weblate (Toki Pona)

Currently translated at 95.0% (344 of 362 strings)

Co-authored-by: Bee Crankson <ProfB.crankson@gmail.com>
Co-authored-by: Cyborus <cyborus@disroot.org>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/tok/
Translation: TagStudio/Strings
2026-04-20 13:09:56 +00:00
Hosted Weblate
690dfd01c3 Translated using Weblate (Finnish)
Currently translated at 87.0% (315 of 362 strings)

Added translation using Weblate (Finnish)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Jonne Saloranta <saloranta.jonne@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/fi/
Translation: TagStudio/Strings
2026-04-20 13:09:56 +00:00
Hosted Weblate
2c19ea1878 Translated using Weblate (Dutch)
Currently translated at 37.5% (136 of 362 strings)

Co-authored-by: Kristof Hermans <hermans1986@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/nl/
Translation: TagStudio/Strings
2026-04-20 13:09:55 +00:00
Hosted Weblate
484f458485 Translated using Weblate (Italian)
Currently translated at 100.0% (362 of 362 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Octavian <223219150+EdelFlosWeiss@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/it/
Translation: TagStudio/Strings
2026-04-20 13:09:54 +00:00
Hosted Weblate
b8dfa7b040 Translated using Weblate (German)
Currently translated at 100.0% (362 of 362 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Patrick Schüle <patrickschuele90@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/de/
Translation: TagStudio/Strings
2026-04-20 13:09:53 +00:00
Travis Abendshien
c4c749650f fix(tests) remove manditory check for ripgrep 2026-01-24 17:16:32 -08:00
TheBobBobs
d54d46e704 fix: persist entry selection across pages and save scroll positions (#1248)
* fix: persist entry selection across pages and save scroll positions

* fix: add badges to all selected entries not just visible ones
2026-01-22 22:45:36 -08:00
Sola-ris
4c484bc4c6 fix: call ripgrep with explicit utf-8 encoding. (#1199) 2026-01-22 22:04:09 -08:00
13 changed files with 530 additions and 159 deletions

View File

@@ -43,6 +43,7 @@ jobs:
libxcb-xinerama0 \
libxkbcommon-x11-0 \
libyaml-dev \
ripgrep \
x11-utils
- name: Execute pytest

View File

@@ -1,6 +1,6 @@
import enum
import random
from dataclasses import dataclass, replace
from dataclasses import dataclass, field, replace
from pathlib import Path
import structlog
@@ -78,8 +78,9 @@ class BrowsingState:
"""Represent a state of the Library grid view."""
page_index: int = 0
page_positions: dict[int, int] = field(default_factory=dict)
sorting_mode: SortingModeEnum = SortingModeEnum.DATE_ADDED
ascending: bool = True
ascending: bool = False
random_seed: float = 0
show_hidden_entries: bool = False

View File

@@ -4,7 +4,6 @@ from pathlib import Path
import structlog
from tagstudio.core.library.alchemy.enums import BrowsingState
from tagstudio.core.library.alchemy.library import Library
from tagstudio.core.library.alchemy.models import Entry
from tagstudio.core.utils.types import unwrap
@@ -52,16 +51,12 @@ class DupeFilesRegistry:
# The file is not in the library directory
continue
results = self.library.search_library(
BrowsingState.from_path(path_relative), 500
)
entries = self.library.get_entries(results.ids)
if not results:
entry = self.library.get_entry_full_by_path(path_relative)
if entry is None:
# file not in library
continue
files.append(entries[0])
files.append(entry)
if not len(files) > 1:
# only one file in the group, nothing to do

View File

@@ -105,8 +105,8 @@ class RefreshTracker:
),
cwd=library_dir,
capture_output=True,
text=True,
shell=True,
encoding="UTF-8",
)
compiled_ignore_path.unlink()

View File

@@ -496,13 +496,11 @@ class ItemThumb(FlowWidget):
toggle_value: bool,
tag_id: int,
):
if entry_id in self.driver.selected:
if len(self.driver.selected) == 1:
self.driver.main_window.preview_panel.field_containers_widget.update_toggled_tag(
tag_id, toggle_value
)
else:
pass
selected = self.driver._selected
if len(selected) == 1 and entry_id in selected:
self.driver.main_window.preview_panel.field_containers_widget.update_toggled_tag(
tag_id, toggle_value
)
@override
def mouseMoveEvent(self, event: QMouseEvent) -> None: # type: ignore[misc]

View File

@@ -1,9 +1,10 @@
import math
import time
from collections.abc import Iterable
from pathlib import Path
from typing import TYPE_CHECKING, Any, override
from PySide6.QtCore import QPoint, QRect, QSize
from PySide6.QtCore import QPoint, QRect, QSize, Signal
from PySide6.QtGui import QPixmap
from PySide6.QtWidgets import QLayout, QLayoutItem, QScrollArea
@@ -19,6 +20,9 @@ if TYPE_CHECKING:
class ThumbGridLayout(QLayout):
# Id of first visible entry
visible_changed = Signal(int)
def __init__(self, driver: "QtDriver", scroll_area: QScrollArea) -> None:
super().__init__(None)
self.driver: QtDriver = driver
@@ -26,10 +30,6 @@ class ThumbGridLayout(QLayout):
self._item_thumbs: list[ItemThumb] = []
self._items: list[QLayoutItem] = []
# Entry.id -> _entry_ids[index]
self._selected: dict[int, int] = {}
# _entry_ids[index]
self._last_selected: int | None = None
self._entry_ids: list[int] = []
self._entries: dict[int, Entry] = {}
@@ -47,12 +47,14 @@ class ThumbGridLayout(QLayout):
# _entry_ids[StartIndex:EndIndex]
self._last_page_update: tuple[int, int] | None = None
self._scroll_to: int | None = None
def scroll_to(self, entry_id: int):
self._scroll_to = entry_id
def set_entries(self, entry_ids: list[int]):
self.scroll_area.verticalScrollBar().setValue(0)
self._selected.clear()
self._last_selected = None
self._entry_ids = entry_ids
self._entries.clear()
self._tag_entries.clear()
@@ -83,90 +85,20 @@ class ThumbGridLayout(QLayout):
self._last_page_update = None
def select_all(self):
self._selected.clear()
for index, id in enumerate(self._entry_ids):
self._selected[id] = index
self._last_selected = index
def update_selected(self):
for item_thumb in self._item_thumbs:
value = item_thumb.item_id in self.driver._selected
item_thumb.thumb_button.set_selected(value)
for entry_id in self._entry_items:
self._set_selected(entry_id)
def select_inverse(self):
selected = {}
for index, id in enumerate(self._entry_ids):
if id not in self._selected:
selected[id] = index
self._last_selected = index
for id in self._selected:
if id not in selected:
self._set_selected(id, value=False)
for id in selected:
self._set_selected(id)
self._selected = selected
def select_entry(self, entry_id: int):
if entry_id in self._selected:
index = self._selected.pop(entry_id)
if index == self._last_selected:
self._last_selected = None
self._set_selected(entry_id, value=False)
else:
try:
index = self._entry_ids.index(entry_id)
except ValueError:
index = -1
self._selected[entry_id] = index
self._last_selected = index
self._set_selected(entry_id)
def select_to_entry(self, entry_id: int):
index = self._entry_ids.index(entry_id)
if len(self._selected) == 0:
self.select_entry(entry_id)
return
if self._last_selected is None:
self._last_selected = min(self._selected.values(), key=lambda i: abs(index - i))
start = self._last_selected
self._last_selected = index
if start > index:
index, start = start, index
else:
index += 1
for i in range(start, index):
entry_id = self._entry_ids[i]
self._selected[entry_id] = i
self._set_selected(entry_id)
def clear_selected(self):
for entry_id in self._entry_items:
self._set_selected(entry_id, value=False)
self._selected.clear()
self._last_selected = None
def _set_selected(self, entry_id: int, value: bool = True):
if entry_id not in self._entry_items:
return
index = self._entry_items[entry_id]
if index < len(self._item_thumbs):
self._item_thumbs[index].thumb_button.set_selected(value)
def add_tags(self, entry_ids: list[int], tag_ids: list[int]):
def add_tags(self, entry_ids: Iterable[int], tag_ids: Iterable[int]):
for tag_id in tag_ids:
self._tag_entries.setdefault(tag_id, set()).update(entry_ids)
def remove_tags(self, entry_ids: list[int], tag_ids: list[int]):
def remove_tags(self, entry_ids: Iterable[int], tag_ids: Iterable[int]):
for tag_id in tag_ids:
self._tag_entries.setdefault(tag_id, set()).difference_update(entry_ids)
def _fetch_entries(self, ids: list[int]):
def _fetch_entries(self, ids: Iterable[int]):
ids = [id for id in ids if id not in self._entries]
entries = self.driver.lib.get_entries(ids)
for entry in entries:
@@ -263,12 +195,24 @@ class ThumbGridLayout(QLayout):
per_row, width_offset, height_offset = self._size(rect.right())
view_height = self.parentWidget().parentWidget().height()
offset = self.scroll_area.verticalScrollBar().value()
if self._scroll_to is not None:
try:
index = self._entry_ids.index(self._scroll_to)
value = (index // per_row) * height_offset
self.scroll_area.verticalScrollBar().setMaximum(value)
self.scroll_area.verticalScrollBar().setSliderPosition(value)
offset = value
except ValueError:
pass
self._scroll_to = None
visible_rows = math.ceil((view_height + (offset % height_offset)) / height_offset)
offset = int(offset / height_offset)
start = offset * per_row
end = start + (visible_rows * per_row)
self.visible_changed.emit(self._entry_ids[start])
# Load closest off screen rows
start -= per_row * 3
end += per_row * 3
@@ -363,7 +307,7 @@ class ThumbGridLayout(QLayout):
entry_id = self._entry_ids[i]
item_index = self._entry_items[entry_id]
item_thumb = self._item_thumbs[item_index]
item_thumb.thumb_button.set_selected(entry_id in self._selected)
item_thumb.thumb_button.set_selected(entry_id in self.driver._selected)
item_thumb.assign_badge(BadgeType.ARCHIVED, entry_id in self._tag_entries[TAG_ARCHIVED])
item_thumb.assign_badge(BadgeType.FAVORITE, entry_id in self._tag_entries[TAG_FAVORITE])

View File

@@ -17,6 +17,7 @@ import re
import sys
import time
from argparse import Namespace
from collections import OrderedDict
from pathlib import Path
from queue import Queue
from shutil import which
@@ -206,7 +207,8 @@ class QtDriver(DriverMixin, QObject):
self.lib = Library()
self.rm: ResourceManager = ResourceManager()
self.args = args
self.frame_content: list[int] = [] # List of Entry IDs on the current page
self.frame_content: list[int] = [] # List of Entry IDs for the current query
self._selected: OrderedDict[int, None] = OrderedDict()
self.pages_count = 0
self.scrollbar_pos = 0
@@ -258,7 +260,13 @@ class QtDriver(DriverMixin, QObject):
@property
def selected(self) -> list[int]:
return list(self.main_window.thumb_layout._selected.keys())
return list(self._selected.keys())
@property
def last_selected(self) -> int | None:
if len(self._selected) == 0:
return None
return reversed(self._selected).__next__()
def __reset_navigation(self) -> None:
self.browsing_history = History(BrowsingState.show_all())
@@ -564,6 +572,16 @@ class QtDriver(DriverMixin, QObject):
self.main_window.search_field.textChanged.connect(self.update_completions_list)
def on_visible_changed(entry_id: int | None):
current = self.browsing_history.current
page_index = current.page_index
if entry_id is None:
current.page_positions.pop(page_index)
else:
current.page_positions[page_index] = entry_id
self.main_window.thumb_layout.visible_changed.connect(on_visible_changed)
self.archived_updated.connect(
lambda hidden: self.update_badges(
{BadgeType.ARCHIVED: hidden}, origin_id=0, add_tags=False
@@ -758,6 +776,7 @@ class QtDriver(DriverMixin, QObject):
self.main_window.setWindowTitle(self.base_title)
self.frame_content.clear()
self._selected.clear()
if self.color_manager_panel:
self.color_manager_panel.reset()
@@ -852,7 +871,7 @@ class QtDriver(DriverMixin, QObject):
def select_all_action_callback(self):
"""Set the selection to all visible items."""
self.main_window.thumb_layout.select_all()
self.select_all()
self.set_clipboard_menu_viability()
self.set_select_actions_visibility()
@@ -861,7 +880,7 @@ class QtDriver(DriverMixin, QObject):
def select_inverse_action_callback(self):
"""Invert the selection of all visible items."""
self.main_window.thumb_layout.select_inverse()
self.select_inverse()
self.set_clipboard_menu_viability()
self.set_select_actions_visibility()
@@ -869,7 +888,7 @@ class QtDriver(DriverMixin, QObject):
self.main_window.preview_panel.set_selection(self.selected, update_preview=False)
def clear_select_action_callback(self):
self.main_window.thumb_layout.clear_selected()
self.clear_selected()
self.set_select_actions_visibility()
self.set_clipboard_menu_viability()
@@ -1195,16 +1214,16 @@ class QtDriver(DriverMixin, QObject):
def page_move(self, value: int, absolute=False) -> None:
logger.info("page_move", value=value, absolute=absolute)
current = self.browsing_history.current
if not absolute:
value += self.browsing_history.current.page_index
self.browsing_history.push(
self.browsing_history.current.with_page_index(clamp(value, 0, self.pages_count - 1))
)
# TODO: Re-allow selecting entries across multiple pages at once.
# This works fine with additive selection but becomes a nightmare with bridging.
current.page_index += value
else:
current.page_index = value
current.page_index = clamp(current.page_index, 0, self.pages_count - 1)
# TODO: The back mouse button will no longer move to the previous page and
# instead goto the previous query passing a new state to update_browsing_state
# will get this behaviour back but would mess with persisting page scroll positions
self.update_browsing_state()
def navigation_callback(self, delta: int) -> None:
@@ -1265,12 +1284,12 @@ class QtDriver(DriverMixin, QObject):
"""
logger.info("[QtDriver] Selecting Items:", item_id=item_id, append=append, bridge=bridge)
if append:
self.main_window.thumb_layout.select_entry(item_id)
self.select_entry(item_id)
elif bridge:
self.main_window.thumb_layout.select_to_entry(item_id)
self.select_to_entry(item_id)
else:
self.main_window.thumb_layout.clear_selected()
self.main_window.thumb_layout.select_entry(item_id)
self.clear_selected()
self.select_entry(item_id)
self.set_clipboard_menu_viability()
self.set_select_actions_visibility()
@@ -1385,7 +1404,14 @@ class QtDriver(DriverMixin, QObject):
self.thumb_job_queue.all_tasks_done.notify_all()
self.thumb_job_queue.not_full.notify_all()
self.main_window.thumb_layout.set_entries(self.frame_content)
page_size = (
len(self.frame_content) if self.settings.infinite_scroll else self.settings.page_size
)
page = self.browsing_history.current.page_index
start = page * page_size
end = min(start + page_size, len(self.frame_content))
self.main_window.thumb_layout.set_entries(self.frame_content[start:end])
self.main_window.thumb_layout.update()
self.main_window.update()
@@ -1400,8 +1426,11 @@ class QtDriver(DriverMixin, QObject):
add_tags(bool): Flag determining if tags associated with the badges need to be added to
the items. Defaults to True.
"""
item_ids = self.selected if (not origin_id or origin_id in self.selected) else [origin_id]
pending_entries: dict[BadgeType, list[int]] = {}
entry_ids = (
set(self._selected.keys())
if (origin_id == 0 or origin_id in self._selected)
else {origin_id}
)
logger.info(
"[QtDriver][update_badges] Updating ItemThumb badges",
@@ -1410,12 +1439,9 @@ class QtDriver(DriverMixin, QObject):
add_tags=add_tags,
)
for it in self.main_window.thumb_layout._item_thumbs:
if it.item_id in item_ids:
if it.item_id in entry_ids:
for badge_type, value in badge_values.items():
if add_tags:
if not pending_entries.get(badge_type):
pending_entries[badge_type] = []
pending_entries[badge_type].append(it.item_id)
it.toggle_item_tag(it.item_id, value, BADGE_TAGS[badge_type])
it.assign_badge(badge_type, value)
@@ -1424,10 +1450,9 @@ class QtDriver(DriverMixin, QObject):
logger.info(
"[QtDriver][update_badges] Adding tags to updated entries",
pending_entries=pending_entries,
pending_entries=entry_ids,
)
for badge_type, value in badge_values.items():
entry_ids = pending_entries.get(badge_type, [])
tag_ids = [BADGE_TAGS[badge_type]]
if value:
@@ -1455,8 +1480,7 @@ class QtDriver(DriverMixin, QObject):
# search the library
start_time = time.time()
Ignore.get_patterns(self.lib.library_dir, include_global=True)
page_size = 0 if self.settings.infinite_scroll else self.settings.page_size
results = self.lib.search_library(self.browsing_history.current, page_size)
results = self.lib.search_library(self.browsing_history.current, page_size=0)
logger.info("items to render", count=len(results))
end_time = time.time()
@@ -1471,9 +1495,17 @@ class QtDriver(DriverMixin, QObject):
# update page content
self.frame_content = results.ids
page_index = self.browsing_history.current.page_index
if state is None:
entry_id = self.browsing_history.current.page_positions.get(page_index)
else:
entry_id = self.last_selected
if entry_id is not None:
self.main_window.thumb_layout.scroll_to(entry_id)
self.update_thumbs()
# update pagination
page_size = 0 if self.settings.infinite_scroll else self.settings.page_size
if page_size > 0:
self.pages_count = math.ceil(results.total_count / page_size)
else:
@@ -1689,3 +1721,45 @@ class QtDriver(DriverMixin, QObject):
event.accept()
else:
event.ignore()
def select_all(self):
self._selected = OrderedDict.fromkeys(self.frame_content)
self.main_window.thumb_layout.update_selected()
def select_inverse(self):
selected = OrderedDict()
for id in self.frame_content:
if id not in self._selected:
selected[id] = None
self._selected = selected
self.main_window.thumb_layout.update_selected()
def select_entry(self, entry_id: int):
if entry_id in self._selected:
self._selected.pop(entry_id)
else:
self._selected[entry_id] = None
self.main_window.thumb_layout.update_selected()
def select_to_entry(self, entry_id: int):
if len(self._selected) == 0:
self.select_entry(entry_id)
return
last_selected = reversed(self._selected).__next__()
start = self.frame_content.index(last_selected)
end = self.frame_content.index(entry_id)
if start > end:
end, start = start, end
else:
end += 1
for i in range(start, end):
entry_id = self.frame_content[i]
self._selected[entry_id] = None
self.main_window.thumb_layout.update_selected()
def clear_selected(self):
self._selected.clear()
self.main_window.thumb_layout.update_selected()

View File

@@ -309,7 +309,7 @@
"status.library_search_query": "Durchsuche die Bibliothek...",
"status.library_version_expected": "Erwartet:",
"status.library_version_found": "Gefunden:",
"status.library_version_mismatch": "BIbliotheksversion stimmt nicht überein!",
"status.library_version_mismatch": "Bibliotheksversion stimmt nicht überein!",
"status.results": "Ergebnisse",
"status.results.invalid_syntax": "Ungültige Such-Syntax:",
"status.results_found": "{count} Ergebnisse gefunden ({time_span})",
@@ -350,6 +350,9 @@
"trash.dialog.title.singular": "Datei löschen",
"trash.name.generic": "Mülleimer",
"trash.name.windows": "Papierkorb",
"version_modal.description": "Eine neue Version von TagStudio ist verfügbar! Du kannst die neueste Version auf <a href=\"https://github.com/TagStudioDev/TagStudio/releases/latest\">GitHub</a> herunterladen.",
"version_modal.status": "Installierte Version: {installed_version}<br>Letzte veröffentlichte Version: {latest_release_version}",
"version_modal.title": "TagStudio Aktualisierung verfügbar",
"view.size.0": "Mini",
"view.size.1": "Klein",
"view.size.2": "Mittel",

View File

@@ -0,0 +1,323 @@
{
"about.config_path": "Konfiguraatio polku",
"about.description": "TagStudio on valokuvien ja tiedostojen järjestämiseen tarkoitettu sovellus, jonka taustalla oleva tagipohjainen järjestelmä keskittyy antamaan käyttäjälle vapautta ja joustavuutta. Ei suljettuja ohjelmia tai formaatteja, ei valtavaa sivutiedostojen merta eikä tiedostojärjestelmän täydellistä mullistamista.",
"about.documentation": "Dokumentaatio",
"about.license": "Lisenssi",
"about.module.found": "Löytyi",
"about.title": "Tietoa TagStudiosta",
"about.website": "Verkkosivu",
"app.git": "Git Commit",
"app.pre_release": "Pre-Release",
"app.title": "{base_title} - Kirjasto '{library_dir}'",
"color.color_border": "Käytä toissijaista väriä reunalle",
"color.confirm_delete": "Oletko varma, että haluat poistaa värin \"{color_name}\"?",
"color.delete": "Poista tunniste",
"color.import_pack": "Tuo väri paketteja",
"color.name": "Nimi",
"color.namespace.delete.prompt": "Oletko varma, että haluat poistaa tämän värin? Tämä poistaa kaikki värit sen mukana!",
"color.namespace.delete.title": "Poista värin nimi",
"color.new": "Uusi väri",
"color.placeholder": "Väri",
"color.primary": "Pääväri",
"color.primary_required": "Pääväri (Vaadittu)",
"color.secondary": "Toissijainen väri",
"color.title.no_color": "Ei väriä",
"color_manager.title": "Hallitse tunnisteiden värejä",
"dependency.missing.title": "{dependency} Ei löytynyt",
"drop_import.description": "Seuraavat tiedostot vastaavat tiedostoja, jotka ovat jo olemassa kirjastossa",
"drop_import.duplicates_choice.plural": "Seuraavat {count} tiedostoa vastaavat tiedostoja, jotka ovat jo olemassa kirjastossa.",
"drop_import.duplicates_choice.singular": "Seuraava tiedosto vastaa tiedostopolkua, joka on jo olemassa kirjastossa.",
"drop_import.progress.label.initial": "Tuo uusi tiedosto...",
"drop_import.progress.label.plural": "Tuodaan uusia tiedostoja...\n{count} Tiedostoa tuotu.{suffix}",
"drop_import.progress.label.singular": "Uusien tiedostojen tuonti...\n1 tiedosto tuotu",
"drop_import.progress.window_title": "Tuo tiedostoja",
"drop_import.title": "Ristiriitaisia tiedosto(ja)",
"edit.color_manager": "Hallitse tunnisteiden värejä",
"edit.copy_fields": "Kopioi kenttiä",
"edit.paste_fields": "Liitä kenttiä",
"edit.tag_manager": "Hallitse Tagejä",
"entries.duplicate.merge": "Yhdistä kaksoiskappaleita",
"entries.duplicate.merge.label": "Yhdistetään kaksoiskappaleita...",
"entries.duplicate.refresh": "Virkistä kaksoiskappaleet",
"entries.duplicates.description": "Kaksoiskappaleilla tarkoitetaan useita merkintöjä, jotka osoittavat samaan tiedostoon levyllä. Näiden yhdistäminen yhdistää kaikkien kaksoiskappaleiden tunnisteet ja metatiedot yhdeksi yhdistetyksi merkinnäksi. Näitä ei pidä sekoittaa \"kopiotiedostoihin\", jotka ovat tiedostojesi kopioita TagStudion ulkopuolella.",
"entries.generic.refresh_alt": "&Refresh",
"entries.generic.remove.removing": "Poistetaan merkintöjä",
"entries.generic.remove.removing_count": "Poistetaan {count} merkintää...",
"entries.ignored.description": "Tiedostomerkintöjä pidetään \"ohitetuina\", jos ne lisättiin kirjastoon ennen kuin käyttäjän ohitussäännöt ('.ts_ignore'-tiedoston kautta) päivitettiin niiden poissulkemiseksi. Ohitetut tiedostot säilytetään kirjastossa oletusarvoisesti vahingossa tapahtuvan tietojen menetyksen estämiseksi ohitussääntöjä päivitettäessä.",
"entries.ignored.ignored_count": "Ohitetut merkinnät {count}",
"entries.ignored.remove": "Poista ohitetut merkinnät",
"entries.ignored.remove_alt": "Remo&ve Ignored Entries",
"entries.ignored.scanning": "Kirjaston skannaus ohitettujen merkintöjen varalta...",
"entries.ignored.title": "Korjaa ohitetut merkinnät",
"entries.mirror": "&Mirror",
"entries.mirror.confirmation": "Oletko varma, että haluat peilata seuraavat {count} merkintää?",
"entries.mirror.label": "Peilataan {idx}/{total} merkintää...",
"entries.mirror.title": "Peilataan merkintöjä",
"entries.mirror.window_title": "Peilaa merkinnät",
"entries.remove.plural.confirm": "Haluatko varmasti poistaa nämä <b>{count</b> merkintää kirjastostasi? Levyllä olevia tiedostoja ei poisteta.",
"entries.remove.singular.confirm": "Haluatko varmasti poistaa tämän merkinnän kirjastostasi? Levyllä olevia tiedostoja ei poisteta.",
"entries.running.dialog.new_entries": "Lisätään {total} uutta tiedosto merkintää...",
"entries.running.dialog.title": "Lisätään uudet tiedosto merkinnät",
"entries.tags": "Tunnisteet",
"entries.unlinked.description": "Jokainen kirjastomerkintä on linkitetty tiedostoon jossakin hakemistoistasi. Jos merkintään linkitetty tiedosto siirretään tai poistetaan TagStudion ulkopuolelle, sitä pidetään linkittämättömänä.<br><br>Linkittämättömät merkinnät voidaan linkittää automaattisesti uudelleen hakemistojen haun avulla tai poistaa haluttaessa.",
"entries.unlinked.relink.attempting": "Yritetään linkittää uudelleen {index}/{unlinked_count} merkintää, {fixed_count} uudelleenlinkitys onnistui",
"entries.unlinked.relink.manual": "&Manual Relink",
"entries.unlinked.relink.title": "Uudelleen yhdistetään merkintöjä",
"entries.unlinked.remove": "Poista linkittämättömät merkinnät",
"entries.unlinked.remove_alt": "Remo&ve Unlinked Entries",
"entries.unlinked.scanning": "Skannataan kirjastosta linkittämättömiä merkintöjä...",
"entries.unlinked.search_and_relink": "&Search && Relink",
"entries.unlinked.title": "Korjaa linkittämättömät merkinnät",
"entries.unlinked.unlinked_count": "Linkittämättömät merkinnät: {count}",
"ffmpeg.missing.description": "FFmpegiä ja/tai FFprobea ei löytynyt. FFmpeg vaaditaan multimedian toistoon ja pikkukuvien näyttämiseen.",
"ffmpeg.missing.status": "{ffmpeg}: {ffmpeg_status}<br>{ffprobe}: {ffprobe_status}",
"field.copy": "Kopioi kenttä",
"field.edit": "Muokkaa kenttää",
"field.paste": "Liitä kenttä",
"file.date_added": "Päiväys lisätty",
"file.date_created": "Päiväys luotu",
"file.date_modified": "Päiväys muokattu",
"file.dimensions": "Ulottuvuus",
"file.duplicates.description": "TagStudio tukee DupeGuru-tulosten tuontia kaksoiskappaleiden hallintaa varten.",
"file.duplicates.dupeguru.advice": "Peilauksen jälkeen voit käyttää DupeGurua ei-toivottujen tiedostojen poistamiseen. Tämän jälkeen voit poistaa linkittämättömät merkinnät TagStudion Työkalut-valikon \"Korjaa linkittämättömät merkinnät\" -toiminnolla.",
"file.duplicates.dupeguru.file_extension": "DupeGuru tiedostot (*.dupeguru)",
"file.duplicates.dupeguru.load_file": "&Load DupeGuru File",
"file.duplicates.dupeguru.no_file": "Ei valittua DupeGuru-tiedostoa",
"file.duplicates.dupeguru.open_file": "Avaa DupeGuru-tulostiedosto",
"file.duplicates.fix": "Korjaa kaksoiskappale tiedostot",
"file.duplicates.matches": "Kaksoiskappale tiedostoa vastaa: {count}",
"file.duplicates.matches_uninitialized": "Kaksoiskappaletta vastaava tiedosto: Ei saatavilla",
"file.duplicates.mirror.description": "Peilaa syöttötiedot kaikkiin kaksoiskappaleiden osumajoukkoihin yhdistämällä kaikki tiedot poistamatta tai kopioimatta kenttiä. Tämä toiminto ei poista tiedostoja tai tietoja.",
"file.duplicates.mirror_entries": "&Mirror Entries",
"file.duration": "Pituus",
"file.not_found": "Tiedostoa ei löytynyt",
"file.open_file": "Avaa tiedosto",
"file.open_file_with": "Avaa tiedosto",
"file.open_location.generic": "Avaa tiedosto resurssienhallinnassa",
"file.open_location.mac": "Näytä Finderissa",
"file.open_location.windows": "Näytä resurssienhallinnassa",
"file.path": "Tiedoston polku",
"folders_to_tags.close_all": "Sulje kaikki",
"folders_to_tags.converting": "Muutetaan kansiot tageiksi",
"folders_to_tags.description": "Luo tunnisteita kansiorakenteesi perusteella ja lisää ne merkintöihisi.\nAlla oleva rakenne näyttää kaikki luotavat tunnisteet ja mihin merkintöihin niitä lisätään.",
"folders_to_tags.open_all": "Avaa kaikki",
"folders_to_tags.title": "Luo tunnisteet kansioista",
"generic.add": "Lisää",
"generic.apply": "Käytä",
"generic.apply_alt": "&Apply",
"generic.cancel": "Peruuta",
"generic.cancel_alt": "&Cancel",
"generic.close": "Sulje",
"generic.continue": "Jatka",
"generic.copy": "Kopioi",
"generic.cut": "Leikkaa",
"generic.delete": "Poista",
"generic.delete_alt": "&Delete",
"generic.done": "Valmis",
"generic.done_alt": "&Done",
"generic.edit": "Muokkaa",
"generic.edit_alt": "&Edit",
"generic.filename": "Tiedoston nimi",
"generic.missing": "Puuttuu",
"generic.navigation.back": "Takaisin",
"generic.navigation.next": "Seuraava",
"generic.no": "Ei",
"generic.none": "Ei mitään",
"generic.overwrite": "Ylikirjoita",
"generic.overwrite_alt": "&Overwrite",
"generic.paste": "Liitä",
"generic.recent_libraries": "Viimeaikaiset kirjastot",
"generic.remove": "Poista",
"generic.remove_alt": "&Remove",
"generic.rename": "Uudelleen nimeä",
"generic.rename_alt": "&Rename",
"generic.reset": "Aloita alusta",
"generic.save": "Tallenna",
"generic.skip": "Ohita",
"generic.skip_alt": "&Skip",
"generic.yes": "Kyllä",
"home.search": "Etsi",
"home.search_entries": "Hae merkintöjä",
"home.search_library": "Hae kirjasto",
"home.search_tags": "Hae tageja",
"home.show_hidden_entries": "Näytä piiloitetut merkinnät",
"home.thumbnail_size": "Pienoiskuvan koko",
"home.thumbnail_size.extra_large": "Extra isot pienoiskuvakkeet",
"home.thumbnail_size.large": "Isot pienoiskuvat",
"home.thumbnail_size.medium": "Keskikokoiset pienoiskuvat",
"home.thumbnail_size.mini": "Mini pienoiskuvat",
"home.thumbnail_size.small": "Pienet pienoiskuvat",
"ignore.open_file": "Näytä tiedosto \"{ts_ignore}\" levyllä",
"json_migration.checking_for_parity": "Pariteettia tarkistetaan...",
"json_migration.creating_database_tables": "Luodaa SQL tietokannan pöytiä...",
"json_migration.description": "<br>Aloita kirjaston siirtoprosessi ja esikatsele sen tuloksia. Muunnettua kirjastoa <i>ei</i> käytetä, ellet napsauta \"Lopeta siirto\". <br><br>Kirjastotiedoissa tulee olla joko vastaavat arvot tai niissä tulee olla merkintä \"Vastaava\". Arvot, jotka eivät vastaa toisiaan, näkyvät punaisina ja niiden vieressä on symboli \"<b>(!)</b>\".<br><center><i>Tämä prosessi voi kestää useita minuutteja suuremmissa kirjastoissa.</i></center>",
"json_migration.discrepancies_found": "Kirjastossa havaitut ristiriidat",
"json_migration.discrepancies_found.description": "Alkuperäisen ja muunnetun kirjastomuodon välillä havaittiin eroja. Tarkista ja päätä, haluatko jatkaa siirtoa vai peruuttaa sen.",
"json_migration.finish_migration": "Lopeta muutto",
"json_migration.heading.aliases": "Aliaksia:",
"json_migration.heading.colors": "Värit:",
"json_migration.heading.differ": "Poikkeavuus",
"json_migration.heading.extension_list_type": "Laajennusluettelon tyyppi:",
"json_migration.heading.file_extension_list": "Tiedostopääte lista:",
"json_migration.heading.match": "Yhdistetty",
"json_migration.heading.names": "Nimet:",
"json_migration.heading.parent_tags": "Päätunnukset:",
"json_migration.heading.paths": "Polut:",
"json_migration.heading.shorthands": "Lyhenne:",
"json_migration.info.description": "TagStudio-versioilla <b>9.4 ja vanhemmilla</b> luodut kirjaston tallennustiedostot on siirrettävä uuteen <b>v9.5+</b>-muotoon.<br><h2>Tiedot:</h2><ul><li>Olemassa olevaa kirjaston tallennustiedostoasi <b><i>EI</i></b> poisteta</li><li>Henkilökohtaisia tiedostojasi <b><i>EI</i></b> poisteta, siirretä tai muokata</li><li>Uutta v9.5+ -tallennusmuotoa ei voi avata TagStudion aiemmissa versioissa</li></ul><h3>Mitä on muutettu:</h3><ul><li>\"Tunnistekentät\" on korvattu \"Tunnisteluokat\"-toiminnolla. Sen sijaan, että tunnisteet lisättäisiin ensin kenttiin, tunnisteet lisätään nyt suoraan tiedostomerkintöihin. Ne järjestetään sitten automaattisesti luokkiin päätunnisteiden perusteella, jotka on merkitty uudella \"Onko luokka\" -ominaisuudella tunnisteiden muokkausvalikossa. Mikä tahansa tunniste voidaan merkitä luokaksi, ja alitunnisteet lajittelevat itsensä luokiksi merkittyjen päätunnisteiden alle. \"Suosikki\"- ja \"Arkistoitu\"-tunnisteet perivät nyt uuden \"Metatunnisteet\"-tunnisteen, joka on oletusarvoisesti merkitty kategoriaksi.</li><li>Tunnisteiden värejä on muokattu ja laajennettu. Joitakin värejä on nimetty uudelleen tai yhdistetty, mutta kaikki tunnisteiden värit muuntuvat edelleen täsmällisiksi tai lähes vastaaviksi versiossa 9.5.</li></ul><ul>",
"json_migration.migrating_files_entries": "Siirretään {entries:,d} tiedostomerkintää...",
"json_migration.migration_complete": "Muutto valmis!",
"json_migration.migration_complete_with_discrepancies": "Siirto valmis, ristiriitaisuuksia löytyi",
"json_migration.start_and_preview": "Aloita ja esikatsele",
"json_migration.title": "Tallennusmuodon siirto: \"{path}\"",
"json_migration.title.new_lib": "<h2>v9.5+ Kirjasto</h2>",
"json_migration.title.old_lib": "<h2>v9.4 Kirjasto</h2>",
"landing.open_create_library": "Avaa/Luo kirjasto {shortcut}",
"library.field.add": "Lisää kenttä",
"library.field.confirm_remove": "Haluatko varmasti poistaa tämän \"{name}\"-kentän?",
"library.field.mixed_data": "Sekalaista dataa",
"library.field.remove": "Poistettu kenttä",
"library.missing": "Kirjaston sijainti puuttuu",
"library.name": "Kirjasto",
"library.refresh.title": "Virkistetty hakemistot",
"library.scan_library.title": "Skannataan kirjastoa",
"library_info.cleanup": "Puhdistus",
"library_info.cleanup.backups": "Kirjasto varmuuskopiot:",
"library_info.cleanup.dupe_files": "Kaksoiskappaleet:",
"library_info.cleanup.ignored": "Ohitetut merkinnät:",
"library_info.cleanup.legacy_json": "Jäljelle jäänyt Legacy -kirjasto:",
"library_info.cleanup.unlinked": "Yhdistämättömät merkinnät:",
"library_info.stats": "Tilastot",
"library_info.stats.colors": "Tunniste värit:",
"library_info.stats.entries": "Merkinnät:",
"library_info.stats.fields": "Kentät:",
"library_info.stats.macros": "Makrot:",
"library_info.stats.namespaces": "Nimiavaruudet:",
"library_info.stats.tags": "Tunnisteet:",
"library_info.title": "Kirjasto '{library_dir}'",
"library_info.version": "Kirjas formaatti versio: {version}",
"library_object.name": "Nimi",
"library_object.name_required": "Nimi (Vaaditaan)",
"library_object.slug": "ID Slug",
"library_object.slug_required": "ID Slug (Vaadittu)",
"media_player.autoplay": "Automaattinen toisto",
"media_player.loop": "Silmukka",
"menu.edit": "Muokkaa",
"menu.edit.manage_tags": "Hallitse tunnisteita",
"menu.edit.new_tag": "Uusi &Tag",
"menu.file": "&File",
"menu.file.clear_recent_libraries": "Puhdista viimeaikaset",
"menu.file.missing_library.title": "Puuttuva kirjasto",
"menu.file.new_library": "Uusi kirjasto",
"menu.file.open_backups_folder": "Avaa varmuuskopio kansio",
"menu.file.open_library": "Avaa kirjasto",
"menu.file.open_recent_library": "Avaa viimeaikainen",
"menu.file.save_library": "Tallenna kirjasto",
"menu.help": "&Help",
"menu.help.about": "Tietoa",
"menu.macros": "&Macros",
"menu.macros.folders_to_tags": "Kansiot tunnisteiksi",
"menu.select": "Valitse",
"menu.settings": "Asetukset...",
"menu.tools": "&Tools",
"menu.view": "&View",
"menu.view.decrease_thumbnail_size": "Pienennä pienoiskuvakkeen kokoa",
"menu.view.increase_thumbnail_size": "Kasvata pienoiskuvakkeen kokoa",
"menu.view.library_info": "Kirjasto &Information",
"menu.window": "Ikkuna",
"namespace.create.description": "TagStudio käyttää nimiavaruuksia erottaakseen ryhmiä, kuten tunnisteita ja värejä, tavalla, joka helpottaa niiden viemistä ja jakamista. TagStudio varaa \"tagstudio\"-alkuiset nimiavaruudet sisäiseen käyttöön.",
"namespace.create.title": "Luo nimiavaruus",
"namespace.new.button": "Uusi nimiavaruus",
"preview.ignored": "Ohitettu",
"preview.multiple_selection": "<b>{count}</b> Kohdetta valittu",
"preview.no_selection": "Ei kohteita valittuna",
"select.add_tag_to_selected": "Lisää tunniste valittuihin",
"select.all": "Valitse kaikki",
"select.clear": "Poista valinnat",
"select.inverse": "Käännä valinta",
"settings.clear_thumb_cache.title": "Puhdista pienoiskuvien välimuisti",
"settings.dateformat.english": "Englanti",
"settings.dateformat.international": "Kansainvälinen",
"settings.dateformat.label": "Päiväys formaatti",
"settings.dateformat.system": "Järjestelmä",
"settings.filepath.label": "Tiedostopolun näkyvyys",
"settings.filepath.option.full": "Näytä kokonaiset polut",
"settings.filepath.option.name": "Näytä ainoastaan tiedostojen nimet",
"settings.generate_thumbs": "Pienoiskuvan luonti",
"settings.global": "Yleiset asetukset",
"settings.hourformat.label": "24 Tunnin aika",
"settings.infinite_scroll": "Loputon selaaminen",
"settings.language": "Kieli",
"settings.library": "Kirjasto asetukset",
"settings.open_library_on_start": "Avaa kirjasto aloittaaksesi",
"settings.page_size": "Sivun koko",
"settings.show_filenames_in_grid": "Näytä tiedostojen nimet ruudukossa",
"settings.show_recent_libraries": "Näytä viimeaikaiset kirjastot",
"settings.splash.label": "Aloitusnäyttö",
"settings.splash.option.classic": "Classic (9.0)",
"settings.splash.option.default": "Vakio",
"settings.splash.option.goo_gears": "Open Source (9.4)",
"settings.splash.option.ninety_five": "'95 (9.5)",
"settings.splash.option.random": "Satunnainen",
"settings.tag_click_action.add_to_search": "Lisää tunniste hakuun",
"settings.tag_click_action.label": "Tunnisten klikkaus toiminto",
"settings.tag_click_action.open_edit": "Muokkaa tunnistetta",
"settings.tag_click_action.set_search": "Etsi tunnistetta",
"settings.theme.dark": "Tumma",
"settings.theme.label": "Teema:",
"settings.theme.light": "Vaalea",
"settings.theme.system": "Järjestelmä",
"settings.thumb_cache_size.label": "Pienoiskuvakkeiden välimuistin koko",
"settings.title": "Asetukset",
"sorting.direction.ascending": "Nouseva",
"sorting.direction.descending": "Laskeva",
"sorting.mode.random": "Satunnainen",
"splash.opening_library": "Avataan kirjastoa \"{library_path}\"...",
"status.deleted_file_plural": "Poistettiin {count} tiedostoa!",
"status.deleted_file_singular": "Poistettiin 1 tiedosto!",
"status.library_backup_in_progress": "Tallennetaan kirjaston varmuuskopiota...",
"status.library_closed": "Kirjasto suljettu ({time_span})",
"status.library_closing": "Suljetaan kirjastoa...",
"status.library_save_success": "Kirjasto tallennettu ja suljettu!",
"status.library_search_query": "Haetaan kirjastoa...",
"status.library_version_expected": "Odotettu:",
"status.library_version_found": "Löytyi:",
"status.library_version_mismatch": "Kirjasto versio ei vastaa!",
"status.results": "Tulokset",
"tag.add": "Lisää tunniste",
"tag.add.plural": "Lisää tunnisteet",
"tag.add_to_search": "Lisää hakuun",
"tag.aliases": "Aliakset",
"tag.all_tags": "Kaikki tunnisteet",
"tag.choose_color": "Valitse tunnisteen väri",
"tag.color": "Väri",
"tag.create": "Luo tunniste",
"tag.edit": "Muokkaa tunnistetta",
"tag.is_category": "On kategoria",
"tag.is_hidden": "On piilotettu",
"tag.name": "Nimi",
"tag.new": "Uusi tunniste",
"tag.parent_tags": "Ylätunnisteet",
"tag.parent_tags.add": "Lisää ylätunniste(ita)",
"tag.remove": "Poista tunniste",
"tag.search_for_tag": "Etsi tunnistetta",
"tag.shorthand": "Lyhenne",
"tag.tag_name_required": "Tunnisteen nimi (Vaaditaan)",
"tag.view_limit": "Näytä raja:",
"tag_manager.title": "Kirjasto tunnisteet",
"trash.dialog.title.plural": "Poista tiedostoja",
"trash.dialog.title.singular": "Poista tiedosto",
"trash.name.generic": "Roskakori",
"trash.name.windows": "Roskakori",
"version_modal.description": "TagStudion uusi versio on saatavilla! Voit ladata uusimman version <a href=\"https://github.com/TagStudioDev/TagStudio/releases/latest\">Githubista</a>.",
"version_modal.status": "Asennettu versio: {installed_version}<br>Uusin julkaisuversio: {latest_release_version}",
"version_modal.title": "TagStudio päivitys saatavilla",
"view.size.0": "Mini",
"view.size.1": "Pieni",
"view.size.2": "Keskikokoinen",
"view.size.3": "Iso",
"view.size.4": "Extra iso",
"window.message.error_opening_library": "Virhe kirjastoa avatessa.",
"window.title.error": "Virhe",
"window.title.open_create_library": "Avaa/Luo kirjasto"
}

View File

@@ -128,7 +128,7 @@
"generic.paste": "Incolla",
"generic.recent_libraries": "Biblioteche Recenti",
"generic.remove": "Rimuovi",
"generic.remove_alt": "&RImuovi",
"generic.remove_alt": "&Rimuovi",
"generic.rename": "Rinomina",
"generic.rename_alt": "&Rinomina",
"generic.reset": "Ripristina",

View File

@@ -1,5 +1,6 @@
{
"about.config_path": "Configuratie Pad",
"about.description": "TagStudio is een applicatie om foto's en bestanden te organiseren, met een onderliggend systeem gebaseerd op tags, dat zich focust op vrijheid en flexibiliteit bieden aan de gebruiker. Geen gepatenteerde programma's of bestandsformaten, geen zeëen aan sidecarbestanden, en je bestaande bestandsstructuur wordt niet volledig overhoop gegooid.",
"about.documentation": "Documentatie",
"about.license": "Licentie",
"about.module.found": "Gevonden",
@@ -7,6 +8,7 @@
"about.website": "Website",
"app.git": "Git Commit",
"app.pre_release": "Pre-Release",
"app.title": "{base_title} - Bibliotheek '{library_dir}'",
"color.color_border": "Gebruik Secundaire Kleur voor Rand",
"color.confirm_delete": "Weet u zeker dat u de kleur \"{color_name}\" wilt verwijderen?",
"color.delete": "Verwijder Label",
@@ -20,6 +22,9 @@
"color.title.no_color": "Geen Kleur",
"color_manager.title": "Beheer Label Kleuren",
"dependency.missing.title": "{dependency} Niet Gevonden",
"drop_import.description": "Volgende bestanden hebben een pad dat overeenkomt met een reeds bestaand pad in de bibliotheek",
"drop_import.duplicates_choice.plural": "De volgende {count} bestanden hebben een pad dat overeenkomt met een reeds bestaand pad in de bibliotheek.",
"drop_import.duplicates_choice.singular": "Volgend bestand heeft een pad dat overeenkomt met een reeds bestaand pad in de bibliotheek.",
"drop_import.progress.label.initial": "Nieuwe bestanden importeren…",
"drop_import.progress.label.plural": "Nieuwe bestanden importeren…\n{count} bestanden geïmporteerd.{suffix}",
"drop_import.progress.label.singular": "Nieuwe bestanden importeren…\n1 bestand geïmporteerd.{suffix}",
@@ -31,6 +36,7 @@
"edit.tag_manager": "Beheer Labels",
"entries.duplicate.merge": "Dubbele Vermeldingen Samenvoegen",
"entries.duplicate.merge.label": "Dubbele vermeldingen samenvoegen...",
"entries.duplicate.refresh": "Dubbele Invoer Vernieuwen",
"entries.tags": "Labels",
"field.copy": "Veld Kopiëren",
"field.edit": "Veld Aanpassen",

View File

@@ -1,25 +1,25 @@
{
"about.config_path": "nasin pi ante lawa",
"about.description": "ilo Tagstudio li ilo pi lawa lipu li ilo pi lawa sitelen li kepeken nasin pi poki pona. ilo Tagstudio li wile pana e lawa mute tawa jan kepeken. nasin pi open ala li lon ala, en ma pi lipu poka li lon ala, en sina li ante ala e nasin lipu ale sina.",
"about.description": "ilo Tagstudio li ilo pi lawa lipu li ilo pi lawa sitelen li kepeken nasin pi poki pona. ilo Tagstudio li wile pana e lawa mute tawa jan kepeken. nasin taso pi kulupu jan li lon, ma pi lipu poka li lon ala, sina li ante ala e nasin lipu ale sina.",
"about.documentation": "lipu sona",
"about.license": "lipu lawa",
"about.license": "lipu pi ken sina",
"about.module.found": "lukin",
"about.title": "sona pi ilo Tagstudio",
"about.title": "sona pi ilo TagStudio",
"about.website": "lipu linluwi",
"app.git": "Git Commit",
"app.pre_release": "nanpa pi pakala mute",
"app.pre_release": "nanpa pi pakala ken",
"app.title": "{base_title} - tomo '{library_dir}'",
"color.color_border": "o kepeken kule nanpa tu lon selo",
"color.confirm_delete": "sina wile ala wile weka e kule \"{color_name}\"?",
"color.delete": "o weka e poki",
"color.import_pack": "o kama jo e kulupu kule",
"color.name": "nimi",
"color.namespace.delete.prompt": "sina wile ala wile weka e ma nimi kule ni? weka la kule ale lon ma ni li weka kin!",
"color.namespace.delete.title": "o weka e ma nimi kule",
"color.namespace.delete.prompt": "sina wile ala wile weka e kulupu kule ni? weka la kule ale lon ona li weka kin!",
"color.namespace.delete.title": "o weka e kulupu kule",
"color.new": "kule sin",
"color.placeholder": "kule",
"color.primary": "kule nanpa wan",
"color.primary_required": "kule nanpa wan (wile mute)",
"color.primary_required": "kule nanpa wan (ni o lon)",
"color.secondary": "kule nanpa tu",
"color.title.no_color": "kule ala",
"color_manager.title": "o lawa e kule poki",
@@ -28,9 +28,9 @@
"drop_import.duplicates_choice.plural": "lipu {count} ni li jo e nasin lipu sama lon tomo.",
"drop_import.duplicates_choice.singular": "lipu ni li jo e nasin lipu sama lon tomo.",
"drop_import.progress.label.initial": "mi kama jo e lipu sin...",
"drop_import.progress.label.plural": "mi kama jo e lipu sin...\nmi kama jo e lipu {count}.{suffix}",
"drop_import.progress.label.singular": "mi kama jo e lipu sin...\nmi kama jo e lipu wan.{suffix}",
"drop_import.progress.window_title": "o kama jo e lipu",
"drop_import.progress.label.plural": "mi kama jo e lipu sin...\nmi jo e lipu sin {count}.{suffix}",
"drop_import.progress.label.singular": "mi kama jo e lipu sin...\nmi jo e lipu sin 1.{suffix}",
"drop_import.progress.window_title": "o kama jo e lipu sin",
"drop_import.title": "lipu ike",
"edit.color_manager": "o lawa e kule poki",
"edit.copy_fields": "o kama jo e ma sama",
@@ -38,8 +38,8 @@
"edit.tag_manager": "o lawa e poki",
"entries.duplicate.merge": "o wan e ijo sama",
"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.duplicate.refresh": "o sin sin e 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. o sona e ni: \"ijo sama\" li \"lipu sama\" ala.",
"entries.generic.remove.removing": "mi weka e ijo",
"entries.generic.remove.removing_count": "mi weka e ijo {count}...",
"entries.ignored.description": "sina pana e ijo lipu lon tomo, la sina weka e ona lon lawa toki pi lukin ala la, ona li \"lukin ala\". meso la, lipu pi lukin ala li lon tomo tan ni: ni ala la sina ante e lawa toki pi lukin ala la, nanpa li ken pakala.",
@@ -81,7 +81,7 @@
"file.duplicates.dupeguru.advice": "jasima li pini la, sina ken kepeken ilo DupeGuru. ilo DupeGuru li ken weka e ijo ike. ni li pini la, o kepeken nasin \"o pona e ijo pi ijo lon ala\" lon ilo TagStudio. ni li weka e ijo pi ijo lon ala.",
"file.duplicates.dupeguru.file_extension": "ijo DupeGuru (*.dupeguru)",
"file.duplicates.dupeguru.load_file": "o kama &lon e ijo DupeGuru",
"file.duplicates.dupeguru.no_file": "sina o anu e ijo DupeGuru",
"file.duplicates.dupeguru.no_file": "sina jo ala e lipu DupeGuru",
"file.duplicates.dupeguru.open_file": "o open e sona pini tan ilo DupeGuru",
"file.duplicates.fix": "pona e ijo sama",
"file.duplicates.matches": "ijo sama: %{count}",
@@ -146,13 +146,14 @@
"home.thumbnail_size.small": "sitelen lili",
"ignore.open_file": "o ken lukin e lipu \"{ts_ignore}\" lon ilo sina",
"json_migration.checking_for_parity": "mi alasa e nasin tu...",
"json_migration.creating_database_tables": "mi pali e poki SQL mute...",
"json_migration.description": "<br>o open e tawa tomo o lukin e pini. sina pilin ala e \"o pini e tawa\" la, mi kepeken <i>ala</i> e tomo ante. <br><br>sona tomo o jo e nanpa sama anu toki \"sama\" la ale li pona. nanpa ante li loje li jo e sitelen \"<b>(!)</b>\" lon poka ona.<br><center><i>tomo li suli la pali ni li lanpan e tenpo mute.</i></center>",
"json_migration.discrepancies_found": "mi lukin e ike pi tomo sina",
"json_migration.discrepancies_found.description": "mi lukin e ike lon nasin tomo open lon nasin tomo ante. o lukin, o awen tawa anu ala.",
"json_migration.finish_migration": "o pini e tawa",
"json_migration.heading.aliases": "nimi ante:",
"json_migration.heading.colors": "kule:",
"json_migration.heading.differ": "ike",
"json_migration.heading.differ": "ante ike",
"json_migration.heading.file_extension_list": "kulupu pi namako lipu:",
"json_migration.heading.match": "sama",
"json_migration.heading.names": "nimi:",
@@ -181,11 +182,13 @@
"library.scan_library.title": "mi o lukin e tomo",
"library_info.cleanup": "jaki",
"library_info.cleanup.backups": "sama awen tomo:",
"library_info.cleanup.dupe_files": "lipu ni li sama:",
"library_info.cleanup.ignored": "ijo pi lukin ala:",
"library_info.stats": "sona nanpa",
"library_info.stats.colors": "kule poki:",
"library_info.stats.entries": "ijo:",
"library_info.stats.fields": "ma:",
"library_info.stats.macros": "ilo pali:",
"library_info.stats.namespaces": "ma nimi:",
"library_info.stats.tags": "poki:",
"library_info.title": "tomo '{library_dir}'",
@@ -194,8 +197,8 @@
"library_object.name_required": "nimi (wile mute)",
"library_object.slug": "ID Slug",
"library_object.slug_required": "ID Slug (wile mute)",
"macros.running.dialog.new_entries": "mi pali lon ijo sin {count}/{total}...",
"macros.running.dialog.title": "mi pali lon ijo sin",
"macros.running.dialog.new_entries": "mi ilo pali lon ijo sin {count}/{total}...",
"macros.running.dialog.title": "mi ilo pali lon ijo sin",
"media_player.autoplay": "pali pi sina ala",
"media_player.loop": "pali sike",
"menu.delete_selected_files_ambiguous": "o tawa e lipu tawa {trash_term}",
@@ -233,18 +236,18 @@
"menu.view.increase_thumbnail_size": "o suli e sitelen",
"menu.view.library_info": "sona pi tomo n&i",
"menu.window": "lipu",
"namespace.create.description": "ilo TagStuidio li kulupu e poki e kule kepeken nasin pi pana pona la ilo li kepeken ma nimi. ma nimi pi nimi open \"tagstudio\" li ilo TagStudio taso. ilo TagStudio li kepeken ona lon insa.",
"namespace.create.description": "ilo TagStudio li kulupu e poki e kule kepeken nasin pi pana pona la ilo li kepeken ma nimi. ma nimi pi nimi open \"tagstudio\" li ilo TagStudio taso. ilo TagStudio li kepeken ona lon insa.",
"namespace.create.description_color": "kule poki li kepeken ma nimi sama kulupu pi kulupu kule. ale la open la kule ale pi pali sina li lon kulupu pi ma nimi.",
"namespace.create.title": "o pali sin e ma nimi",
"namespace.new.button": "o pali sin e ma nimi",
"namespace.new.prompt": "o pali sin e ma nimi tawa pana e kule sina!",
"preview.ignored": "lukin ala",
"preview.multiple_selection": "sina jo e ijo <b>{count}</b>",
"preview.no_selection": "ijo ala li anu",
"preview.no_selection": "sina jo e ijo ala",
"select.add_tag_to_selected": "o pana e poki tawa jo sina",
"select.all": "o jo e ale",
"select.clear": "o weka e jo sina",
"select.inverse": "o jasima e ni",
"select.inverse": "o jasima e jo sina",
"settings.clear_thumb_cache.title": "o weka e poki sitelen",
"settings.dateformat.english": "nasin Inli",
"settings.dateformat.international": "nasin pi ma mute",
@@ -258,13 +261,15 @@
"settings.hourformat.label": "tenpo pi kipisi 24",
"settings.language": "toki",
"settings.library": "lawa toki pi tomo mi",
"settings.open_library_on_start": "ilo Tagstudio li open la o open e tomo ni",
"settings.open_library_on_start": "ilo TagStudio li open la o open e tomo",
"settings.page_size": "suli lipu",
"settings.restart_required": "sina open sin e ilo Tagstudio la ante li lon.",
"settings.restart_required": "sina open sin e ilo TagStudio la ante li lon.",
"settings.show_filenames_in_grid": "o sitelen e nimi ijo lon leko sitelen",
"settings.show_recent_libraries": "o sitelen e tomo pi tenpo poka",
"settings.splash.label": "sitelen open",
"settings.splash.option.random": "nasa",
"settings.tag_click_action.add_to_search": "o pana e poki tawa alasa",
"settings.tag_click_action.label": "pali pi pilin poki",
"settings.tag_click_action.open_edit": "o ante e poki",
"settings.tag_click_action.set_search": "o alasa e poki",
"settings.theme.dark": "pimeja",
@@ -275,6 +280,7 @@
"settings.title": "lawa toki",
"sorting.direction.ascending": "tawa sewi",
"sorting.direction.descending": "tawa anpa",
"sorting.mode.random": "nasa",
"splash.opening_library": "mi open e tomo \"{library_path}\"...",
"status.deleted_file_plural": "mi weka e lipu {count}!",
"status.deleted_file_singular": "mi weka e lipu 1!",
@@ -321,8 +327,8 @@
"trash.context.ambiguous": "o tawa e lipu tawa {trash_term}",
"trash.context.plural": "o tawa e lipu tawa {trash_term}",
"trash.context.singular": "o tawa e lipu tawa {trash_term}",
"trash.dialog.disambiguation_warning.plural": "ni li weka e ona tan ilo Tagstudio <i>tan</i> nasin lipu sina!",
"trash.dialog.disambiguation_warning.singular": "ni li weka e ona tan ilo Tagstudio <i>tan</i> nasin lipu sina!",
"trash.dialog.disambiguation_warning.plural": "ni li weka e ona tan ilo TagStudio <i>tan</i> nasin lipu sina!",
"trash.dialog.disambiguation_warning.singular": "ni li weka e ona tan ilo TagStudio <i>tan</i> nasin lipu sina!",
"trash.dialog.move.confirmation.plural": "sina wile ala wile tawa e lipu ni {count} tawa {trash_term}?",
"trash.dialog.move.confirmation.singular": "sina wile ala wile tawa e lipu ni tawa {trash_term}?",
"trash.dialog.permanent_delete_warning": "<b>toki suli!</b> lipu ni li ken ala tawa {trash_term} la, ona li <b>weka lon tenpo ale!</b>",
@@ -330,9 +336,9 @@
"trash.dialog.title.singular": "o weka e lipu",
"trash.name.generic": "poki pi ijo weka",
"trash.name.windows": "poki pi ijo weka",
"version_modal.description": "nanpa sin pi ilo Tagstudio li lon! sina ken kama jo e ona tan <a href=\"https://github.com/TagStudioDev/TagStudio/releases/latest\">ma Github</a>.",
"version_modal.description": "nanpa sin pi ilo TagStudio li lon! sina ken kama jo e ona tan <a href=\"https://github.com/TagStudioDev/TagStudio/releases/latest\">ma Github</a>.",
"version_modal.status": "nanpa ni: {installed_version}<br>nanpa sin: {latest_release_version}",
"version_modal.title": "nanpa sin pi ilo Tagstudio li lon",
"version_modal.title": "nanpa sin pi ilo TagStudio li lon",
"view.size.0": "lili mute",
"view.size.1": "lili",
"view.size.2": "meso",

View File

@@ -29,3 +29,23 @@ def test_refresh_new_files(library: Library, exclude_mode: bool):
# Test if the single file was added
list(registry.refresh_dir(library_dir, force_internal_tools=True))
assert registry.files_not_in_library == [Path("FOO.MD")]
@pytest.mark.parametrize("library", [TemporaryDirectory()], indirect=True)
def test_refresh_multi_byte_filenames(library: Library):
library_dir = unwrap(library.library_dir)
# Given
registry = RefreshTracker(library=library)
library.included_files.clear()
(library_dir / ".TagStudio").mkdir()
(library_dir / "こんにちは.txt").touch()
(library_dir / "emdash.txt").touch()
(library_dir / "apostrophe.txt").touch()
(library_dir / "umlaute äöü.txt").touch()
# Test if all files were added with their correct names and without exceptions
list(registry.refresh_dir(library_dir))
assert Path("こんにちは.txt") in registry.files_not_in_library
assert Path("emdash.txt") in registry.files_not_in_library
assert Path("apostrophe.txt") in registry.files_not_in_library
assert Path("umlaute äöü.txt") in registry.files_not_in_library