Compare commits

..

4 Commits

Author SHA1 Message Date
Hosted Weblate
ffc2680bf7 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-15 17:10:05 +00:00
Hosted Weblate
ddc1077e96 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-15 17:10:05 +00:00
Hosted Weblate
09c2d7e2b5 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-15 17:10:04 +00:00
Hosted Weblate
0ca5dc03e5 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-15 17:10:04 +00:00
8 changed files with 47 additions and 101 deletions

View File

@@ -48,17 +48,17 @@ jobs:
macos:
strategy:
matrix:
os-version: ['14', '15']
os-version: ['13', '14']
include:
- os-version: '14'
- os-version: '13'
arch: x86_64
- os-version: '15'
- os-version: '14'
arch: aarch64
runs-on: macos-${{ matrix.os-version }}
env:
# INFO: Even though we run on 14, target towards compatibility
# INFO: Even though we run on 13, target towards compatibility
MACOSX_DEPLOYMENT_TARGET: '11.0'
steps:

View File

@@ -5,7 +5,7 @@ build-backend = "hatchling.build"
[project]
name = "TagStudio"
description = "A User-Focused Photo & File Management System."
version = "9.5.7"
version = "9.5.6"
license = "GPL-3.0-only"
readme = "README.md"
requires-python = ">=3.12,<3.13"

View File

@@ -2,7 +2,7 @@
# Licensed under the GPL-3.0 License.
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
VERSION: str = "9.5.7" # Major.Minor.Patch
VERSION: str = "9.5.6" # Major.Minor.Patch
VERSION_BRANCH: str = "" # Usually "" or "Pre-Release"
# The folder & file names where TagStudio keeps its data relative to a library.

View File

@@ -42,7 +42,6 @@ from sqlalchemy import (
text,
update,
)
from sqlalchemy.dialects import sqlite
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import (
InstanceState,
@@ -1479,7 +1478,7 @@ class Library:
return None
def add_tags_to_entries(
self, entry_ids: int | Iterable[int], tag_ids: int | Iterable[int]
self, entry_ids: int | list[int] | set[int], tag_ids: int | list[int] | set[int]
) -> int:
"""Add one or more tags to one or more entries.
@@ -1495,57 +1494,45 @@ class Library:
entry_ids_ = [entry_ids] if isinstance(entry_ids, int) else entry_ids
tag_ids_ = [tag_ids] if isinstance(tag_ids, int) else tag_ids
values: list[tuple[int, int]] = []
for tag_id in tag_ids_:
values.extend((tag_id, entry_id) for entry_id in entry_ids_)
with Session(self.engine, expire_on_commit=False) as session:
for sub_list in [
values[i : i + MAX_SQL_VARIABLES // 2]
for i in range(0, len(values), MAX_SQL_VARIABLES // 2)
]:
stmt = (
sqlite.insert(TagEntry)
.values(sub_list)
.on_conflict_do_nothing()
.returning(TagEntry)
)
added = session.scalars(stmt).all()
total_added += len(added)
session.commit()
for tag_id in tag_ids_:
for entry_id in entry_ids_:
try:
session.add(TagEntry(tag_id=tag_id, entry_id=entry_id))
total_added += 1
session.commit()
except IntegrityError:
session.rollback()
return total_added
def remove_tags_from_entries(
self, entry_ids: int | Iterable[int], tag_ids: int | Iterable[int]
):
self, entry_ids: int | list[int] | set[int], tag_ids: int | list[int] | set[int]
) -> bool:
"""Remove one or more tags from one or more entries."""
logger.info(
"[Library][remove_tags_from_entries]",
entry_ids=entry_ids,
tag_ids=tag_ids,
)
entry_ids_ = [entry_ids] if isinstance(entry_ids, int) else list(entry_ids)
tag_ids_ = [tag_ids] if isinstance(tag_ids, int) else list(tag_ids)
entry_ids_ = [entry_ids] if isinstance(entry_ids, int) else entry_ids
tag_ids_ = [tag_ids] if isinstance(tag_ids, int) else tag_ids
with Session(self.engine, expire_on_commit=False) as session:
for tags_sub_list in [
tag_ids_[i : i + MAX_SQL_VARIABLES // 2]
for i in range(0, len(tag_ids_), MAX_SQL_VARIABLES // 2)
]:
for entries_sub_list in [
entry_ids_[i : i + MAX_SQL_VARIABLES // 2]
for i in range(0, len(entry_ids_), MAX_SQL_VARIABLES // 2)
]:
stmt = delete(TagEntry).where(
and_(
TagEntry.tag_id.in_(tags_sub_list),
TagEntry.entry_id.in_(entries_sub_list),
)
)
session.execute(stmt)
session.commit()
try:
for tag_id in tag_ids_:
for entry_id in entry_ids_:
tag_entry = session.scalars(
select(TagEntry).where(
and_(
TagEntry.tag_id == tag_id,
TagEntry.entry_id == entry_id,
)
)
).first()
if tag_entry:
session.delete(tag_entry)
session.flush()
session.commit()
return True
except IntegrityError as e:
logger.error(e)
session.rollback()
return False
def add_color(self, color_group: TagColorGroup) -> TagColorGroup | None:
with Session(self.engine, expire_on_commit=False) as session:

View File

@@ -306,13 +306,13 @@ class QtDriver(DriverMixin, QObject):
sys.argv += ["-platform", "windows:darkmode=2"]
self.app = QApplication(sys.argv)
self.app.setStyle("Fusion")
# Apply theme color if explicitly set to DARK or LIGHT by the user.
# For SYSTEM, we let Qt decide based on OS theme.
if self.settings.theme == Theme.DARK:
if self.settings.theme == Theme.SYSTEM:
# TODO: detect theme instead of always setting dark
self.app.styleHints().setColorScheme(Qt.ColorScheme.Dark)
elif self.settings.theme == Theme.LIGHT:
self.app.styleHints().setColorScheme(Qt.ColorScheme.Light)
else:
self.app.styleHints().setColorScheme(
Qt.ColorScheme.Dark if self.settings.theme == Theme.DARK else Qt.ColorScheme.Light
)
if (
platform.system() == "Darwin" or platform.system() == "Windows"

View File

@@ -29,7 +29,7 @@
"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 {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ä",
@@ -54,7 +54,7 @@
"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.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",

View File

@@ -1,6 +1,5 @@
{
"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",
@@ -8,7 +7,6 @@
"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",
@@ -22,9 +20,6 @@
"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}",
@@ -36,7 +31,6 @@
"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,35 +0,0 @@
# Copyright (C) 2025
# Licensed under the GPL-3.0 License.
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
"""Test theme handling in QtDriver, particularly the SYSTEM theme fix (issue #999)."""
from unittest.mock import Mock
import pytest
from PySide6.QtCore import Qt
from tagstudio.qt.global_settings import Theme
@pytest.mark.parametrize(
"theme,expected_call",
[
(Theme.DARK, Qt.ColorScheme.Dark),
(Theme.LIGHT, Qt.ColorScheme.Light),
(Theme.SYSTEM, None), # SYSTEM theme should NOT call setColorScheme
],
)
def test_theme_colorscheme_handling(theme: Theme, expected_call):
mock_style_hints = Mock()
if theme == Theme.DARK:
mock_style_hints.setColorScheme(Qt.ColorScheme.Dark)
elif theme == Theme.LIGHT:
mock_style_hints.setColorScheme(Qt.ColorScheme.Light)
if expected_call is None:
# SYSTEM theme should NOT call setColorScheme
mock_style_hints.setColorScheme.assert_not_called()
else:
mock_style_hints.setColorScheme.assert_called_once_with(expected_call)