diff --git a/AGENTS.md b/AGENTS.md index cc6d70eb8..e1555a5b8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -32,9 +32,12 @@ This file tells automated coding agents (and humans) where to find the single so - Auto-generated configuration and command references live under `specs/generated/`. Agents MUST NOT read, analyse, or modify anything in this directory; refer humans to `specs/generated/README.md` if regeneration is required. - Regenerate `NOTICE` files with `make notice` when dependencies change (e.g., updates to `go.mod`, `go.sum`, `package-lock.json`, or other lockfiles). Do not edit `NOTICE` or `frontend/NOTICE` manually. - When writing CLI examples or scripts, place option flags before positional arguments unless the command requires a different order. +- Use RFC 3339 UTC timestamps in request and response examples, and valid ID, UID and UUID examples in docs and tests. > Document headings must use **Title Case** (in APA or AP style) across Markdown files to keep generated navigation and changelogs consistent. Always spell the product name as `PhotoPrism`; this proper noun is an exception to generic naming rules. +> Refresh the `**Last Updated:**` date at the top of documents whenever you make changes to their contents, using the format `January 20, 2026` (without time); leave it as-is for simple formatting or whitespace-only edits. + ## Safety & Data - If `git status` shows unexpected changes, assume a human might be editing; if you think you caused them, ask for permission before using reset commands like `git checkout` or `git reset`. @@ -162,8 +165,6 @@ Note: Across our public documentation, official images, and in production, the c - JS/Vue: use the lint/format scripts in `frontend/package.json` (ESLint + Prettier) - All added code and tests **must** be formatted according to our standards. -> Remember to update the `**Last Updated:**` line at the top whenever you edit these guidelines or other files containing a timestamp. - ## Tests - From within the Development Environment: @@ -183,7 +184,7 @@ Note: Across our public documentation, official images, and in production, the c Use `playwright__browser_navigate` to open `/library/login`, sign in, and then call `playwright__browser_take_screenshot` to capture the page state. - **Viewport Defaults** — Desktop sessions open with a `1280×900` viewport by default. Use `playwright__browser_resize` if the viewport is not preconfigured or you need to adjust it mid-run. -- **Mobile Workflows** — When testing responsive layouts, use the `playwright_mobile` server (for example, `playwright_mobile__browser_navigate`). +- **Mobile Workflows** — When testing responsive layouts, use the `playwright_mobile` server (for example, `playwright_mobile__browser_navigate`). It launches with a `375×667` viewport, matching a typical smartphone display, so you can capture mobile layouts without manual resizing. - **Authentication** — Default admin credentials are `admin` / `photoprism`: - If login fails, check your active Compose file or container environment for `PHOTOPRISM_ADMIN_USER` and `PHOTOPRISM_ADMIN_PASSWORD`. diff --git a/Dockerfile b/Dockerfile index 4595a1b5b..553492f81 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Ubuntu 25.10 (Questing Quokka) -FROM photoprism/develop:251211-questing +FROM photoprism/develop:260121-questing # Harden npm usage by default (applies to npm ci / install in dev container) ENV NPM_CONFIG_IGNORE_SCRIPTS=true diff --git a/NOTICE b/NOTICE index dd1881a7f..480299f54 100644 --- a/NOTICE +++ b/NOTICE @@ -9,7 +9,7 @@ The following 3rd-party software packages may be used by or distributed with PhotoPrism. Any information relevant to third-party vendors listed below are collected using common, reasonable means. -Date generated: 2025-12-12 +Date generated: 2026-01-21 ================================================================================ @@ -408,8 +408,8 @@ SOFTWARE. -------------------------------------------------------------------------------- Package: github.com/clipperhouse/displaywidth -Version: v0.6.0 -License: MIT (https://github.com/clipperhouse/displaywidth/blob/v0.6.0/LICENSE) +Version: v0.7.0 +License: MIT (https://github.com/clipperhouse/displaywidth/blob/v0.7.0/LICENSE) MIT License @@ -464,8 +464,8 @@ SOFTWARE. -------------------------------------------------------------------------------- Package: github.com/clipperhouse/uax29/v2 -Version: v2.3.0 -License: MIT (https://github.com/clipperhouse/uax29/blob/v2.3.0/LICENSE) +Version: v2.3.1 +License: MIT (https://github.com/clipperhouse/uax29/blob/v2.3.1/LICENSE) MIT License @@ -1122,8 +1122,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- Package: github.com/gabriel-vasile/mimetype -Version: v1.4.11 -License: MIT (https://github.com/gabriel-vasile/mimetype/blob/v1.4.11/LICENSE) +Version: v1.4.12 +License: MIT (https://github.com/gabriel-vasile/mimetype/blob/v1.4.12/LICENSE) MIT License @@ -1234,8 +1234,8 @@ THE SOFTWARE. -------------------------------------------------------------------------------- Package: github.com/go-co-op/gocron/v2 -Version: v2.18.2 -License: MIT (https://github.com/go-co-op/gocron/blob/v2.18.2/LICENSE) +Version: v2.19.0 +License: MIT (https://github.com/go-co-op/gocron/blob/v2.19.0/LICENSE) MIT License @@ -2471,8 +2471,8 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI -------------------------------------------------------------------------------- Package: github.com/golang/geo -Version: v0.0.0-20251209161508-25c597310d4b -License: Apache-2.0 (https://github.com/golang/geo/blob/25c597310d4b/LICENSE) +Version: v0.0.0-20260120070133-792bb8583fbb +License: Apache-2.0 (https://github.com/golang/geo/blob/792bb8583fbb/LICENSE) Apache License @@ -4530,8 +4530,8 @@ SOFTWARE. -------------------------------------------------------------------------------- Package: github.com/olekukonko/errors -Version: v1.1.0 -License: MIT (https://github.com/olekukonko/errors/blob/v1.1.0/LICENSE) +Version: v1.2.0 +License: MIT (https://github.com/olekukonko/errors/blob/v1.2.0/LICENSE) MIT License @@ -4558,8 +4558,8 @@ SOFTWARE. -------------------------------------------------------------------------------- Package: github.com/olekukonko/ll -Version: v0.1.3 -License: MIT (https://github.com/olekukonko/ll/blob/v0.1.3/LICENSE) +Version: v0.1.4-0.20260115111900-9e59c2286df0 +License: MIT (https://github.com/olekukonko/ll/blob/9e59c2286df0/LICENSE) MIT License @@ -4586,8 +4586,8 @@ SOFTWARE. -------------------------------------------------------------------------------- Package: github.com/olekukonko/tablewriter -Version: v1.1.2 -License: MIT (https://github.com/olekukonko/tablewriter/blob/v1.1.2/LICENSE.md) +Version: v1.1.3 +License: MIT (https://github.com/olekukonko/tablewriter/blob/v1.1.3/LICENSE.md) Copyright (C) 2014 by Oleku Konko @@ -5353,8 +5353,8 @@ License: Apache-2.0 (https://github.com/prometheus/client_model/blob/v0.6.2/LICE -------------------------------------------------------------------------------- Package: github.com/prometheus/common -Version: v0.67.4 -License: Apache-2.0 (https://github.com/prometheus/common/blob/v0.67.4/LICENSE) +Version: v0.67.5 +License: Apache-2.0 (https://github.com/prometheus/common/blob/v0.67.5/LICENSE) Apache License Version 2.0, January 2004 @@ -5889,8 +5889,8 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI -------------------------------------------------------------------------------- Package: github.com/sirupsen/logrus -Version: v1.9.3 -License: MIT (https://github.com/sirupsen/logrus/blob/v1.9.3/LICENSE) +Version: v1.9.4 +License: MIT (https://github.com/sirupsen/logrus/blob/v1.9.4/LICENSE) The MIT License (MIT) @@ -6026,8 +6026,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- Package: github.com/ugjka/go-tz/v2 -Version: v2.2.6 -License: MIT (https://github.com/ugjka/go-tz/blob/v2.2.6/LICENSE) +Version: v2.2.7 +License: MIT (https://github.com/ugjka/go-tz/blob/v2.2.7/LICENSE) MIT License @@ -6376,8 +6376,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- Package: github.com/yalue/onnxruntime_go -Version: v1.24.0 -License: MIT (https://github.com/yalue/onnxruntime_go/blob/v1.24.0/LICENSE) +Version: v1.25.0 +License: MIT (https://github.com/yalue/onnxruntime_go/blob/v1.25.0/LICENSE) Copyright (c) 2023 Nathan Otterness @@ -6610,8 +6610,8 @@ License: Apache-2.0 (https://github.com/zitadel/logging/blob/v0.6.2/LICENSE) -------------------------------------------------------------------------------- Package: github.com/zitadel/oidc/v3 -Version: v3.45.1 -License: Apache-2.0 (https://github.com/zitadel/oidc/blob/v3.45.1/LICENSE) +Version: v3.45.3 +License: Apache-2.0 (https://github.com/zitadel/oidc/blob/v3.45.3/LICENSE) Apache License Version 2.0, January 2004 @@ -6818,10 +6818,10 @@ License: Apache-2.0 (https://github.com/zitadel/oidc/blob/v3.45.1/LICENSE) -------------------------------------------------------------------------------- Package: github.com/zitadel/schema -Version: v1.3.1 -License: BSD-3-Clause (https://github.com/zitadel/schema/blob/v1.3.1/LICENSE) +Version: v1.3.2 +License: BSD-3-Clause (https://github.com/zitadel/schema/blob/v1.3.2/LICENSE) -Copyright (c) 2012 Rodrigo Moraes. All rights reserved. +Copyright (c) 2023 The Gorilla Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are @@ -7060,8 +7060,8 @@ License: Apache-2.0 (https://github.com/open-telemetry/opentelemetry-go-instrume -------------------------------------------------------------------------------- Package: go.opentelemetry.io/otel -Version: v1.38.0 -License: Apache-2.0 (https://github.com/open-telemetry/opentelemetry-go/blob/v1.38.0/LICENSE) +Version: v1.39.0 +License: Apache-2.0 (https://github.com/open-telemetry/opentelemetry-go/blob/v1.39.0/LICENSE) Apache License Version 2.0, January 2004 @@ -7297,8 +7297,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- Package: go.opentelemetry.io/otel/metric -Version: v1.38.0 -License: Apache-2.0 (https://github.com/open-telemetry/opentelemetry-go/blob/metric/v1.38.0/metric/LICENSE) +Version: v1.39.0 +License: Apache-2.0 (https://github.com/open-telemetry/opentelemetry-go/blob/metric/v1.39.0/metric/LICENSE) Apache License Version 2.0, January 2004 @@ -7534,8 +7534,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- Package: go.opentelemetry.io/otel/trace -Version: v1.38.0 -License: Apache-2.0 (https://github.com/open-telemetry/opentelemetry-go/blob/trace/v1.38.0/trace/LICENSE) +Version: v1.39.0 +License: Apache-2.0 (https://github.com/open-telemetry/opentelemetry-go/blob/trace/v1.39.0/trace/LICENSE) Apache License Version 2.0, January 2004 @@ -8188,8 +8188,8 @@ License: Apache-2.0 (https://github.com/go4org/go4/blob/214862532bf5/LICENSE) -------------------------------------------------------------------------------- Package: golang.org/x/crypto -Version: v0.46.0 -License: BSD-3-Clause (https://cs.opensource.google/go/x/crypto/+/v0.46.0:LICENSE) +Version: v0.47.0 +License: BSD-3-Clause (https://cs.opensource.google/go/x/crypto/+/v0.47.0:LICENSE) Copyright 2009 The Go Authors. @@ -8222,8 +8222,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- Package: golang.org/x/image -Version: v0.34.0 -License: BSD-3-Clause (https://cs.opensource.google/go/x/image/+/v0.34.0:LICENSE) +Version: v0.35.0 +License: BSD-3-Clause (https://cs.opensource.google/go/x/image/+/v0.35.0:LICENSE) Copyright 2009 The Go Authors. @@ -8256,8 +8256,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- Package: golang.org/x/mod/semver -Version: v0.31.0 -License: BSD-3-Clause (https://cs.opensource.google/go/x/mod/+/v0.31.0:LICENSE) +Version: v0.32.0 +License: BSD-3-Clause (https://cs.opensource.google/go/x/mod/+/v0.32.0:LICENSE) Copyright 2009 The Go Authors. @@ -8290,8 +8290,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- Package: golang.org/x/net -Version: v0.48.0 -License: BSD-3-Clause (https://cs.opensource.google/go/x/net/+/v0.48.0:LICENSE) +Version: v0.49.0 +License: BSD-3-Clause (https://cs.opensource.google/go/x/net/+/v0.49.0:LICENSE) Copyright 2009 The Go Authors. @@ -8324,8 +8324,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- Package: golang.org/x/oauth2 -Version: v0.33.0 -License: BSD-3-Clause (https://cs.opensource.google/go/x/oauth2/+/v0.33.0:LICENSE) +Version: v0.34.0 +License: BSD-3-Clause (https://cs.opensource.google/go/x/oauth2/+/v0.34.0:LICENSE) Copyright 2009 The Go Authors. @@ -8392,8 +8392,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- Package: golang.org/x/sys -Version: v0.39.0 -License: BSD-3-Clause (https://cs.opensource.google/go/x/sys/+/v0.39.0:LICENSE) +Version: v0.40.0 +License: BSD-3-Clause (https://cs.opensource.google/go/x/sys/+/v0.40.0:LICENSE) Copyright 2009 The Go Authors. @@ -8426,8 +8426,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- Package: golang.org/x/text -Version: v0.32.0 -License: BSD-3-Clause (https://cs.opensource.google/go/x/text/+/v0.32.0:LICENSE) +Version: v0.33.0 +License: BSD-3-Clause (https://cs.opensource.google/go/x/text/+/v0.33.0:LICENSE) Copyright 2009 The Go Authors. @@ -8494,8 +8494,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- Package: gonum.org/v1/gonum -Version: v0.16.0 -License: BSD-3-Clause (https://github.com/gonum/gonum/blob/v0.16.0/LICENSE) +Version: v0.17.0 +License: BSD-3-Clause (https://github.com/gonum/gonum/blob/v0.17.0/LICENSE) Copyright ©2013 The Gonum Authors. All rights reserved. diff --git a/assets/locales/lv/default.po b/assets/locales/lv/default.po new file mode 100644 index 000000000..5fc60da57 --- /dev/null +++ b/assets/locales/lv/default.po @@ -0,0 +1,417 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: ci@photoprism.app\n" +"POT-Creation-Date: 2025-10-17 17:32+0000\n" +"PO-Revision-Date: 2026-01-15 09:02+0000\n" +"Last-Translator: Janis Eglitis \n" +"Language-Team: none\n" +"Language: lv\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n % 10 == 0 || n % 100 >= 11 && n % 100 <= " +"19) ? 0 : ((n % 10 == 1 && n % 100 != 11) ? 1 : 2);\n" +"X-Generator: Weblate 5.15.1\n" + +#: messages.go:104 +msgid "Something went wrong, try again" +msgstr "Kaut kas nogāja greizi, mēģiniet vēlreiz" + +#: messages.go:105 +msgid "Unable to do that" +msgstr "To nevar izdarīt" + +#: messages.go:106 +msgid "Changes could not be saved" +msgstr "Izmaiņas nevarēja saglabāt" + +#: messages.go:107 +msgid "Could not be deleted" +msgstr "Nevarēja izdzēst" + +#: messages.go:108 +#, c-format +msgid "%s already exists" +msgstr "%s jau pastāv" + +#: messages.go:109 +msgid "Not found" +msgstr "Nav atrasts" + +#: messages.go:110 +msgid "File not found" +msgstr "Fails nav atrasts" + +#: messages.go:111 +msgid "File too large" +msgstr "Fails ir pārāk liels" + +#: messages.go:112 +msgid "Unsupported" +msgstr "Neatbalstīts" + +#: messages.go:113 +msgid "Unsupported type" +msgstr "Neatbalstīts tips" + +#: messages.go:114 +msgid "Unsupported format" +msgstr "Neatbalstīts formāts" + +#: messages.go:115 +msgid "Originals folder is empty" +msgstr "Oriģinālu mape ir tukša" + +#: messages.go:116 +msgid "Selection not found" +msgstr "Izvēle nav atrasta" + +#: messages.go:117 +msgid "Entity not found" +msgstr "Vienība nav atrasta" + +#: messages.go:118 +msgid "Account not found" +msgstr "Konts nav atrasts" + +#: messages.go:119 +msgid "User not found" +msgstr "Lietotājs nav atrasts" + +#: messages.go:120 +msgid "Label not found" +msgstr "Birka nav atrasta" + +#: messages.go:121 +msgid "Album not found" +msgstr "Albums nav atrasts" + +#: messages.go:122 +msgid "Subject not found" +msgstr "Tēma nav atrasta" + +#: messages.go:123 +msgid "Person not found" +msgstr "Persona nav atrasta" + +#: messages.go:124 +msgid "Face not found" +msgstr "Seja nav atrasta" + +#: messages.go:125 +msgid "Not available in public mode" +msgstr "Nav pieejams publiskajā režīmā" + +#: messages.go:126 +msgid "Not available in read-only mode" +msgstr "Nav pieejams lasīšanas režīmā" + +#: messages.go:127 +msgid "Please log in to your account" +msgstr "Lūdzu, piesakieties savā kontā" + +#: messages.go:128 +msgid "Permission denied" +msgstr "Atļauja liegta" + +#: messages.go:129 +msgid "Payment required" +msgstr "Nepieciešams maksājums" + +#: messages.go:130 +msgid "Upload might be offensive" +msgstr "Augšupielāde varētu būt aizskaroša" + +#: messages.go:131 +msgid "Upload failed" +msgstr "Augšupielāde neizdevās" + +#: messages.go:132 +msgid "No items selected" +msgstr "Nav izvēlēts neviens ieraksts" + +#: messages.go:133 +msgid "Failed creating file, please check permissions" +msgstr "Neizdevās izveidot failu, lūdzu, pārbaudiet atļaujas" + +#: messages.go:134 +msgid "Failed creating folder, please check permissions" +msgstr "Neizdevās izveidot mapi, lūdzu, pārbaudiet atļaujas" + +#: messages.go:135 +msgid "Could not connect, please try again" +msgstr "Nevarēja izveidot savienojumu, lūdzu, mēģiniet vēlreiz" + +#: messages.go:136 +msgid "Enter verification code" +msgstr "Ievadiet verifikācijas kodu" + +#: messages.go:137 +msgid "Invalid verification code, please try again" +msgstr "Nederīgs verifikācijas kods, lūdzu, mēģiniet vēlreiz" + +#: messages.go:138 +msgid "Invalid password, please try again" +msgstr "Nederīga parole, lūdzu, mēģiniet vēlreiz" + +#: messages.go:139 +msgid "Feature disabled" +msgstr "Funkcionalitāte ir izslēgta" + +#: messages.go:140 +msgid "No labels selected" +msgstr "Nav atlasītas nevienas birkas" + +#: messages.go:141 +msgid "No albums selected" +msgstr "Nav atlasīts neviens albums" + +#: messages.go:142 +msgid "No files available for download" +msgstr "Nav lejupielādei pieejamu failu" + +#: messages.go:143 +msgid "Failed to create zip file" +msgstr "Neizdevās izveidot zip failu" + +#: messages.go:144 +msgid "Invalid credentials" +msgstr "Nederīgs lietotājvārds vai parole" + +#: messages.go:145 +msgid "Invalid link" +msgstr "Nederīga saite" + +#: messages.go:146 +msgid "Invalid name" +msgstr "Nederīgs nosaukums" + +#: messages.go:147 +msgid "Busy, please try again later" +msgstr "Aizņemts, lūdzu, mēģiniet vēlreiz vēlāk" + +#: messages.go:148 +#, c-format +msgid "The wakeup interval is %s, but must be 1h or less" +msgstr "Pamošanās intervāls ir %s, bet tam jābūt 1 h vai mazākam" + +#: messages.go:149 +msgid "Your account could not be connected" +msgstr "Jūsu kontu nevarēja savienot" + +#: messages.go:150 +msgid "Too many requests" +msgstr "Pārāk daudz pieprasījumu" + +#: messages.go:151 +msgid "Insufficient storage" +msgstr "Nepietiek brīvas vietas" + +#: messages.go:152 +msgid "Quota exceeded" +msgstr "Kvota pārsniegta" + +#: messages.go:155 +msgid "Changes successfully saved" +msgstr "Izmaiņas veiksmīgi saglabātas" + +#: messages.go:156 +msgid "Album created" +msgstr "Albums izveidots" + +#: messages.go:157 +msgid "Album saved" +msgstr "Albums saglabāts" + +#: messages.go:158 +#, c-format +msgid "Album %s deleted" +msgstr "Albums %s ir dzēsts" + +#: messages.go:159 +msgid "Album contents cloned" +msgstr "Albuma saturs ir dublēts" + +#: messages.go:160 +msgid "File removed from stack" +msgstr "Fails izņemts no saraksta" + +#: messages.go:161 +msgid "File deleted" +msgstr "Fails izdzēsts" + +#: messages.go:162 +#, c-format +msgid "Selection added to %s" +msgstr "Atlasījums pievienots %s" + +#: messages.go:163 +#, c-format +msgid "One entry added to %s" +msgstr "Viens ieraksts pievienots %s" + +#: messages.go:164 +#, c-format +msgid "%d entries added to %s" +msgstr "%d ieraksti pievienoti %s" + +#: messages.go:165 +#, c-format +msgid "One entry removed from %s" +msgstr "Viens ieraksts noņemts no %s" + +#: messages.go:166 +#, c-format +msgid "%d entries removed from %s" +msgstr "%d ieraksti noņemti no %s" + +#: messages.go:167 +msgid "Account created" +msgstr "Konts izveidots" + +#: messages.go:168 +msgid "Account saved" +msgstr "Konts saglabāts" + +#: messages.go:169 +msgid "Account deleted" +msgstr "Konts ir dzēsts" + +#: messages.go:170 +msgid "Settings saved" +msgstr "Iestatījumi saglabāti" + +#: messages.go:171 +msgid "Password changed" +msgstr "Parole nomainīta" + +#: messages.go:172 +#, c-format +msgid "Import completed in %d s" +msgstr "Importēšana pabeigta %d s laikā" + +#: messages.go:173 +msgid "Import canceled" +msgstr "Importēšana atcelta" + +#: messages.go:174 +#, c-format +msgid "Indexing completed in %d s" +msgstr "Indeksēšana pabeigta %d s laikā" + +#: messages.go:175 +msgid "Indexing originals..." +msgstr "Notiek oriģinālu indeksēšana..." + +#: messages.go:176 +#, c-format +msgid "Indexing files in %s" +msgstr "Notiek failu indeksēšana %s" + +#: messages.go:177 +msgid "Indexing canceled" +msgstr "Indeksēšana atcelta" + +#: messages.go:178 +#, c-format +msgid "Removed %d files and %d photos" +msgstr "Noņemti %d faili un %d fotoattēli" + +#: messages.go:179 +#, c-format +msgid "Moving files from %s" +msgstr "Failu pārvietošana no %s" + +#: messages.go:180 +#, c-format +msgid "Copying files from %s" +msgstr "Failu kopēšana no %s" + +#: messages.go:181 +msgid "Labels deleted" +msgstr "Birkas ir dzēstas" + +#: messages.go:182 +msgid "Label saved" +msgstr "Birka saglabāta" + +#: messages.go:183 +msgid "Subject saved" +msgstr "Tēma saglabāta" + +#: messages.go:184 +msgid "Subject deleted" +msgstr "Tēma dzēsta" + +#: messages.go:185 +msgid "Person saved" +msgstr "Persona saglabāta" + +#: messages.go:186 +msgid "Person deleted" +msgstr "Persona dzēsta" + +#: messages.go:187 +msgid "File uploaded" +msgstr "Fails augšupielādēts" + +#: messages.go:188 +#, c-format +msgid "%d files uploaded in %d s" +msgstr "%d faili augšupielādēti %d sekundēs" + +#: messages.go:189 +msgid "Processing upload..." +msgstr "Notiek augšupielādes apstrāde..." + +#: messages.go:190 +msgid "Upload has been processed" +msgstr "Augšupielāde ir apstrādāta" + +#: messages.go:191 +msgid "Selection approved" +msgstr "Atlase apstiprināta" + +#: messages.go:192 +msgid "Selection archived" +msgstr "Atlase arhivēta" + +#: messages.go:193 +msgid "Selection restored" +msgstr "Atlase atjaunota" + +#: messages.go:194 +msgid "Selection marked as private" +msgstr "Izvēle atzīmēta kā privāta" + +#: messages.go:195 +msgid "Albums deleted" +msgstr "Albumi ir dzēsti" + +#: messages.go:196 +#, c-format +msgid "Zip created in %d s" +msgstr "ZIP fails izveidots %d sekundēs" + +#: messages.go:197 +msgid "Permanently deleted" +msgstr "Neatgriezeniski dzēsts" + +#: messages.go:198 +#, c-format +msgid "%s has been restored" +msgstr "%s ir atjaunots" + +#: messages.go:199 +msgid "Successfully verified" +msgstr "Veiksmīgi verificēts" + +#: messages.go:200 +msgid "Successfully activated" +msgstr "Veiksmīgi aktivizēts" diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 51255cd44..1d1210f8c 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -9,24 +9,24 @@ "version": "1", "license": "AGPL-3.0", "dependencies": { - "@babel/cli": "^7.28.3", - "@babel/core": "^7.28.5", + "@babel/cli": "^7.28.6", + "@babel/core": "^7.28.6", "@babel/plugin-transform-runtime": "^7.28.5", - "@babel/preset-env": "^7.28.5", - "@babel/register": "^7.28.3", - "@babel/runtime": "^7.28.4", + "@babel/preset-env": "^7.28.6", + "@babel/register": "^7.28.6", + "@babel/runtime": "^7.28.6", "@eslint/eslintrc": "^3.3.3", "@eslint/js": "^9.33.0", "@mdi/font": "^7.4.47", "@testing-library/jest-dom": "^6.9.1", - "@testing-library/react": "^16.3.0", + "@testing-library/react": "^16.3.2", "@vitejs/plugin-react": "^5.1.2", "@vitejs/plugin-vue": "^6.0.3", "@vitest/browser": "^3.2.4", "@vitest/coverage-v8": "^3.2.4", "@vitest/ui": "^3.2.4", "@vue/compiler-sfc": "^3.5.18", - "@vue/language-server": "^3.1.8", + "@vue/language-server": "^3.2.2", "@vue/test-utils": "^2.4.6", "@vvo/tzdb": "^6.198.0", "axios": "^1.13.2", @@ -41,14 +41,14 @@ "css-loader": "^7.1.2", "cssnano": "^7.1.2", "escape-string-regexp": "^4.0.0", - "eslint": "^9.39.1", + "eslint": "^9.39.2", "eslint-config-prettier": "^10.1.8", "eslint-formatter-pretty": "^6.0.1", "eslint-plugin-html": "^8.1.3", "eslint-plugin-import": "^2.32.0", "eslint-plugin-node": "^11.1.0", - "eslint-plugin-prettier": "^5.5.4", - "eslint-plugin-vue": "^10.6.2", + "eslint-plugin-prettier": "^5.5.5", + "eslint-plugin-vue": "^10.7.0", "eslint-plugin-vuetify": "^2.5.3", "eslint-webpack-plugin": "^5.0.2", "eventsource-polyfill": "^0.9.6", @@ -60,9 +60,9 @@ "i": "^0.3.7", "jsdom": "^26.1.0", "luxon": "^3.7.2", - "maplibre-gl": "^5.14.0", + "maplibre-gl": "^5.16.0", "memoize-one": "^6.0.0", - "mini-css-extract-plugin": "^2.9.4", + "mini-css-extract-plugin": "^2.10.0", "minimist": "^1.2.8", "node-storage-shim": "^2.0.1", "passive-events-support": "^1.1.0", @@ -71,20 +71,20 @@ "postcss": "^8.5.6", "postcss-import": "^16.1.1", "postcss-loader": "^8.2.0", - "postcss-preset-env": "^10.5.0", + "postcss-preset-env": "^10.6.1", "postcss-reporter": "^7.1.0", "postcss-url": "^10.1.3", - "prettier": "^3.7.4", + "prettier": "^3.8.0", "pubsub-js": "^1.9.5", "regenerator-runtime": "^0.14.1", "resolve-url-loader": "^5.0.0", "sanitize-html": "^2.17.0", - "sass": "^1.96.0", + "sass": "^1.97.2", "sass-loader": "^16.0.6", "sockette": "^2.0.6", "style-loader": "^4.0.0", "svg-url-loader": "^8.0.0", - "tar": "^7.5.2", + "tar": "^7.5.6", "url-loader": "^4.1.1", "util": "^0.12.5", "vite-tsconfig-paths": "^5.1.4", @@ -98,15 +98,15 @@ "vue-sanitize-directive": "^0.2.1", "vue-style-loader": "^4.1.3", "vue3-gettext": "^2.4.0", - "vuetify": "^3.11.3", - "webpack": "^5.103.0", + "vuetify": "^3.11.7", + "webpack": "^5.104.1", "webpack-bundle-analyzer": "^4.10.2", "webpack-cli": "^6.0.1", "webpack-hot-middleware": "^2.26.1", "webpack-manifest-plugin": "^5.0.1", "webpack-md5-hash": "^0.0.6", "webpack-merge": "^6.0.1", - "webpack-plugin-vuetify": "^3.1.2", + "webpack-plugin-vuetify": "^3.1.3", "workbox-webpack-plugin": "^7.4.0" }, "engines": { @@ -154,9 +154,9 @@ "license": "ISC" }, "node_modules/@babel/cli": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.28.3.tgz", - "integrity": "sha512-n1RU5vuCX0CsaqaXm9I0KUCNKNQMy5epmzl/xdSSm70bSqhg9GWhgeosypyQLc0bK24+Xpk1WGzZlI9pJtkZdg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.28.6.tgz", + "integrity": "sha512-6EUNcuBbNkj08Oj4gAZ+BUU8yLCgKzgVX4gaTh09Ya2C8ICM4P+G30g4m3akRxSYAp3A/gnWchrNst7px4/nUQ==", "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.28", @@ -183,12 +183,12 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", + "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -197,29 +197,29 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", - "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz", + "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", - "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz", + "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helpers": "^7.28.4", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.5", - "@babel/types": "^7.28.5", + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -236,13 +236,13 @@ } }, "node_modules/@babel/generator": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", - "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz", + "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.5", - "@babel/types": "^7.28.5", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -264,12 +264,12 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.27.2", + "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", @@ -280,17 +280,17 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.5.tgz", - "integrity": "sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", + "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1", + "@babel/helper-replace-supers": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.28.5", + "@babel/traverse": "^7.28.6", "semver": "^6.3.1" }, "engines": { @@ -356,27 +356,27 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", "license": "MIT", "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", - "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.28.3" + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -398,9 +398,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -424,14 +424,14 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", - "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", + "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", "license": "MIT", "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -481,39 +481,39 @@ } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.3.tgz", - "integrity": "sha512-zdf983tNfLZFletc0RRXYrHrucBEg95NIFMkn6K9dbeMYnsgHaSBGcQqdsCSStG2PYwRre0Qc2NNSCXbG+xc6g==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.6.tgz", + "integrity": "sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==", "license": "MIT", "dependencies": { - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.3", - "@babel/types": "^7.28.2" + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", - "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", "license": "MIT", "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4" + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", - "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", + "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", "license": "MIT", "dependencies": { - "@babel/types": "^7.28.5" + "@babel/types": "^7.28.6" }, "bin": { "parser": "bin/babel-parser.js" @@ -586,13 +586,13 @@ } }, "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.3.tgz", - "integrity": "sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.6.tgz", + "integrity": "sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.28.3" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -614,12 +614,12 @@ } }, "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz", - "integrity": "sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.28.6.tgz", + "integrity": "sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -629,12 +629,12 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", - "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -675,14 +675,14 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.0.tgz", - "integrity": "sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.6.tgz", + "integrity": "sha512-9knsChgsMzBV5Yh3kkhrZNxH3oCYAfMBkNNaVN4cP2RVlFPe8wYdwwcnOsAbkdDoV9UjFtOXWrWB52M8W4jNeA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-remap-async-to-generator": "^7.27.1", - "@babel/traverse": "^7.28.0" + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -692,13 +692,13 @@ } }, "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz", - "integrity": "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.28.6.tgz", + "integrity": "sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==", "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-remap-async-to-generator": "^7.27.1" }, "engines": { @@ -724,12 +724,12 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.5.tgz", - "integrity": "sha512-45DmULpySVvmq9Pj3X9B+62Xe+DJGov27QravQJU1LLcapR6/10i+gYVAucGGJpHBp5mYxIMK4nDAT/QDLr47g==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.6.tgz", + "integrity": "sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -739,13 +739,13 @@ } }, "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", - "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.28.6.tgz", + "integrity": "sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==", "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -755,13 +755,13 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.3.tgz", - "integrity": "sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.6.tgz", + "integrity": "sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==", "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.28.3", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -771,17 +771,17 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz", - "integrity": "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.6.tgz", + "integrity": "sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==", "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-globals": "^7.28.0", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1", - "@babel/traverse": "^7.28.4" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -791,13 +791,13 @@ } }, "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz", - "integrity": "sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.28.6.tgz", + "integrity": "sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/template": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/template": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -823,13 +823,13 @@ } }, "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz", - "integrity": "sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.28.6.tgz", + "integrity": "sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==", "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -854,13 +854,13 @@ } }, "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz", - "integrity": "sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.28.6.tgz", + "integrity": "sha512-5suVoXjC14lUN6ZL9OLKIHCNVWCrqGqlmEp/ixdXjvgnEl/kauLvvMO/Xw9NyMc95Joj1AeLVPVMvibBgSoFlA==", "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -885,13 +885,13 @@ } }, "node_modules/@babel/plugin-transform-explicit-resource-management": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.0.tgz", - "integrity": "sha512-K8nhUcn3f6iB+P3gwCv/no7OdzOZQcKchW6N389V6PD8NUWKZHzndOd9sPDVbMoBsbmjMqlB4L9fm+fEFNVlwQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.6.tgz", + "integrity": "sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.28.0" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -901,12 +901,12 @@ } }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.5.tgz", - "integrity": "sha512-D4WIMaFtwa2NizOp+dnoFjRez/ClKiC2BqqImwKd1X28nqBtZEyCYJ2ozQrrzlxAFrcrjxo39S6khe9RNDlGzw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.6.tgz", + "integrity": "sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -964,12 +964,12 @@ } }, "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz", - "integrity": "sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.28.6.tgz", + "integrity": "sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -994,12 +994,12 @@ } }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.5.tgz", - "integrity": "sha512-axUuqnUTBuXyHGcJEVVh9pORaN6wC5bYfE7FGzPiaWa3syib9m7g+/IT/4VgCOe2Upef43PHzeAvcrVek6QuuA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.6.tgz", + "integrity": "sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1040,13 +1040,13 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", - "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz", + "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==", "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1121,12 +1121,12 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", - "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.28.6.tgz", + "integrity": "sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1136,12 +1136,12 @@ } }, "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz", - "integrity": "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.28.6.tgz", + "integrity": "sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1151,16 +1151,16 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.4.tgz", - "integrity": "sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.6.tgz", + "integrity": "sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==", "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.28.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5", "@babel/plugin-transform-parameters": "^7.27.7", - "@babel/traverse": "^7.28.4" + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1186,12 +1186,12 @@ } }, "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz", - "integrity": "sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.28.6.tgz", + "integrity": "sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1201,12 +1201,12 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.5.tgz", - "integrity": "sha512-N6fut9IZlPnjPwgiQkXNhb+cT8wQKFlJNqcZkWlcTqkcqx6/kU4ynGmLFoa4LViBSirn05YAwk+sQBbPfxtYzQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.6.tgz", + "integrity": "sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { @@ -1232,13 +1232,13 @@ } }, "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz", - "integrity": "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.28.6.tgz", + "integrity": "sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==", "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1248,14 +1248,14 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz", - "integrity": "sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.28.6.tgz", + "integrity": "sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1310,12 +1310,12 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.4.tgz", - "integrity": "sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.6.tgz", + "integrity": "sha512-eZhoEZHYQLL5uc1gS5e9/oTknS0sSSAtd5TkKMUp3J+S/CaUjagc0kOUPsEbDmMeva0nC3WWl4SxVY6+OBuxfw==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1325,13 +1325,13 @@ } }, "node_modules/@babel/plugin-transform-regexp-modifiers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz", - "integrity": "sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.28.6.tgz", + "integrity": "sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==", "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1391,12 +1391,12 @@ } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz", - "integrity": "sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.28.6.tgz", + "integrity": "sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { @@ -1467,13 +1467,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz", - "integrity": "sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.28.6.tgz", + "integrity": "sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==", "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1499,13 +1499,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz", - "integrity": "sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.28.6.tgz", + "integrity": "sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==", "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1515,75 +1515,75 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.5.tgz", - "integrity": "sha512-S36mOoi1Sb6Fz98fBfE+UZSpYw5mJm0NUHtIKrOuNcqeFauy1J6dIvXm2KRVKobOSaGq4t/hBXdN4HGU3wL9Wg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.6.tgz", + "integrity": "sha512-GaTI4nXDrs7l0qaJ6Rg06dtOXTBCG6TMDB44zbqofCIC4PqC7SEvmFFtpxzCDw9W5aJ7RKVshgXTLvLdBFV/qw==", "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.28.5", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", + "@babel/compat-data": "^7.28.6", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.3", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.6", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-import-assertions": "^7.27.1", - "@babel/plugin-syntax-import-attributes": "^7.27.1", + "@babel/plugin-syntax-import-assertions": "^7.28.6", + "@babel/plugin-syntax-import-attributes": "^7.28.6", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.27.1", - "@babel/plugin-transform-async-generator-functions": "^7.28.0", - "@babel/plugin-transform-async-to-generator": "^7.27.1", + "@babel/plugin-transform-async-generator-functions": "^7.28.6", + "@babel/plugin-transform-async-to-generator": "^7.28.6", "@babel/plugin-transform-block-scoped-functions": "^7.27.1", - "@babel/plugin-transform-block-scoping": "^7.28.5", - "@babel/plugin-transform-class-properties": "^7.27.1", - "@babel/plugin-transform-class-static-block": "^7.28.3", - "@babel/plugin-transform-classes": "^7.28.4", - "@babel/plugin-transform-computed-properties": "^7.27.1", + "@babel/plugin-transform-block-scoping": "^7.28.6", + "@babel/plugin-transform-class-properties": "^7.28.6", + "@babel/plugin-transform-class-static-block": "^7.28.6", + "@babel/plugin-transform-classes": "^7.28.6", + "@babel/plugin-transform-computed-properties": "^7.28.6", "@babel/plugin-transform-destructuring": "^7.28.5", - "@babel/plugin-transform-dotall-regex": "^7.27.1", + "@babel/plugin-transform-dotall-regex": "^7.28.6", "@babel/plugin-transform-duplicate-keys": "^7.27.1", - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.28.6", "@babel/plugin-transform-dynamic-import": "^7.27.1", - "@babel/plugin-transform-explicit-resource-management": "^7.28.0", - "@babel/plugin-transform-exponentiation-operator": "^7.28.5", + "@babel/plugin-transform-explicit-resource-management": "^7.28.6", + "@babel/plugin-transform-exponentiation-operator": "^7.28.6", "@babel/plugin-transform-export-namespace-from": "^7.27.1", "@babel/plugin-transform-for-of": "^7.27.1", "@babel/plugin-transform-function-name": "^7.27.1", - "@babel/plugin-transform-json-strings": "^7.27.1", + "@babel/plugin-transform-json-strings": "^7.28.6", "@babel/plugin-transform-literals": "^7.27.1", - "@babel/plugin-transform-logical-assignment-operators": "^7.28.5", + "@babel/plugin-transform-logical-assignment-operators": "^7.28.6", "@babel/plugin-transform-member-expression-literals": "^7.27.1", "@babel/plugin-transform-modules-amd": "^7.27.1", - "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.28.6", "@babel/plugin-transform-modules-systemjs": "^7.28.5", "@babel/plugin-transform-modules-umd": "^7.27.1", "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", "@babel/plugin-transform-new-target": "^7.27.1", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", - "@babel/plugin-transform-numeric-separator": "^7.27.1", - "@babel/plugin-transform-object-rest-spread": "^7.28.4", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6", + "@babel/plugin-transform-numeric-separator": "^7.28.6", + "@babel/plugin-transform-object-rest-spread": "^7.28.6", "@babel/plugin-transform-object-super": "^7.27.1", - "@babel/plugin-transform-optional-catch-binding": "^7.27.1", - "@babel/plugin-transform-optional-chaining": "^7.28.5", + "@babel/plugin-transform-optional-catch-binding": "^7.28.6", + "@babel/plugin-transform-optional-chaining": "^7.28.6", "@babel/plugin-transform-parameters": "^7.27.7", - "@babel/plugin-transform-private-methods": "^7.27.1", - "@babel/plugin-transform-private-property-in-object": "^7.27.1", + "@babel/plugin-transform-private-methods": "^7.28.6", + "@babel/plugin-transform-private-property-in-object": "^7.28.6", "@babel/plugin-transform-property-literals": "^7.27.1", - "@babel/plugin-transform-regenerator": "^7.28.4", - "@babel/plugin-transform-regexp-modifiers": "^7.27.1", + "@babel/plugin-transform-regenerator": "^7.28.6", + "@babel/plugin-transform-regexp-modifiers": "^7.28.6", "@babel/plugin-transform-reserved-words": "^7.27.1", "@babel/plugin-transform-shorthand-properties": "^7.27.1", - "@babel/plugin-transform-spread": "^7.27.1", + "@babel/plugin-transform-spread": "^7.28.6", "@babel/plugin-transform-sticky-regex": "^7.27.1", "@babel/plugin-transform-template-literals": "^7.27.1", "@babel/plugin-transform-typeof-symbol": "^7.27.1", "@babel/plugin-transform-unicode-escapes": "^7.27.1", - "@babel/plugin-transform-unicode-property-regex": "^7.27.1", + "@babel/plugin-transform-unicode-property-regex": "^7.28.6", "@babel/plugin-transform-unicode-regex": "^7.27.1", - "@babel/plugin-transform-unicode-sets-regex": "^7.27.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.28.6", "@babel/preset-modules": "0.1.6-no-external-plugins", "babel-plugin-polyfill-corejs2": "^0.4.14", "babel-plugin-polyfill-corejs3": "^0.13.0", @@ -1613,9 +1613,9 @@ } }, "node_modules/@babel/register": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.28.3.tgz", - "integrity": "sha512-CieDOtd8u208eI49bYl4z1J22ySFw87IGwE+IswFEExH7e3rLgKb0WNQeumnacQ1+VoDJLYI5QFA3AJZuyZQfA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.28.6.tgz", + "integrity": "sha512-pgcbbEl/dWQYb6L6Yew6F94rdwygfuv+vJ/tXfwIOYAfPB6TNWpXUMEtEq3YuTeHRdvMIhvz13bkT9CNaS+wqA==", "license": "MIT", "dependencies": { "clone-deep": "^4.0.1", @@ -1632,40 +1632,40 @@ } }, "node_modules/@babel/runtime": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", - "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", - "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz", + "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.5", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6", "debug": "^4.3.1" }, "engines": { @@ -1673,9 +1673,9 @@ } }, "node_modules/@babel/types": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", - "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", + "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -2518,9 +2518,9 @@ } }, "node_modules/@csstools/postcss-normalize-display-values": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.0.tgz", - "integrity": "sha512-HlEoG0IDRoHXzXnkV4in47dzsxdsjdz6+j7MLjaACABX2NfvjFS6XVAnpaDyGesz9gK2SC7MbNwdCHusObKJ9Q==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.1.tgz", + "integrity": "sha512-TQUGBuRvxdc7TgNSTevYqrL8oItxiwPDixk20qCB5me/W8uF7BPbhRrAvFuhEoywQp/woRsUZ6SJ+sU5idZAIA==", "funding": [ { "type": "github", @@ -2618,6 +2618,32 @@ "postcss": "^8.4" } }, + "node_modules/@csstools/postcss-property-rule-prelude-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-property-rule-prelude-list/-/postcss-property-rule-prelude-list-1.0.0.tgz", + "integrity": "sha512-IxuQjUXq19fobgmSSvUDO7fVwijDJaZMvWQugxfEUxmjBeDCVaDuMpsZ31MsTm5xbnhA+ElDi0+rQ7sQQGisFA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, "node_modules/@csstools/postcss-random-function": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@csstools/postcss-random-function/-/postcss-random-function-2.0.1.tgz", @@ -2753,6 +2779,31 @@ "postcss": "^8.4" } }, + "node_modules/@csstools/postcss-syntax-descriptor-syntax-production": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-syntax-descriptor-syntax-production/-/postcss-syntax-descriptor-syntax-production-1.0.1.tgz", + "integrity": "sha512-GneqQWefjM//f4hJ/Kbox0C6f2T7+pi4/fqTqOFGTL3EjnvOReTqO1qUQ30CaUjkwjYq9qZ41hzarrAxCc4gow==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-tokenizer": "^3.0.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, "node_modules/@csstools/postcss-system-ui-font-family": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@csstools/postcss-system-ui-font-family/-/postcss-system-ui-font-family-1.0.0.tgz", @@ -2985,9 +3036,9 @@ "license": "MIT" }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", "cpu": [ "ppc64" ], @@ -3001,9 +3052,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", "cpu": [ "arm" ], @@ -3017,9 +3068,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", "cpu": [ "arm64" ], @@ -3033,9 +3084,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", "cpu": [ "x64" ], @@ -3049,9 +3100,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", "cpu": [ "arm64" ], @@ -3065,9 +3116,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", "cpu": [ "x64" ], @@ -3081,9 +3132,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", "cpu": [ "arm64" ], @@ -3097,9 +3148,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", "cpu": [ "x64" ], @@ -3113,9 +3164,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", "cpu": [ "arm" ], @@ -3129,9 +3180,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", "cpu": [ "arm64" ], @@ -3145,9 +3196,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", "cpu": [ "ia32" ], @@ -3161,9 +3212,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", "cpu": [ "loong64" ], @@ -3177,9 +3228,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", "cpu": [ "mips64el" ], @@ -3193,9 +3244,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", "cpu": [ "ppc64" ], @@ -3209,9 +3260,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", "cpu": [ "riscv64" ], @@ -3225,9 +3276,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", "cpu": [ "s390x" ], @@ -3241,9 +3292,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", "cpu": [ "x64" ], @@ -3257,9 +3308,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", "cpu": [ "arm64" ], @@ -3273,9 +3324,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", "cpu": [ "x64" ], @@ -3289,9 +3340,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", "cpu": [ "arm64" ], @@ -3305,9 +3356,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", "cpu": [ "x64" ], @@ -3321,9 +3372,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", "cpu": [ "arm64" ], @@ -3337,9 +3388,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", "cpu": [ "x64" ], @@ -3353,9 +3404,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", "cpu": [ "arm64" ], @@ -3369,9 +3420,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", "cpu": [ "ia32" ], @@ -3385,9 +3436,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", "cpu": [ "x64" ], @@ -3401,9 +3452,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", - "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "license": "MIT", "dependencies": { "eslint-visitor-keys": "^3.4.3" @@ -3513,9 +3564,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.39.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz", - "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==", + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3940,9 +3991,9 @@ } }, "node_modules/@maplibre/maplibre-gl-style-spec": { - "version": "24.4.0", - "resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-24.4.0.tgz", - "integrity": "sha512-VVuNV2Yf0+yQoth4qbdIPE0qKS6nIG5Atki9BVHZ7R7+0lZyxqxwrh0XVNA5YkuKuytFg/1i3VMyJQnp2EtOqw==", + "version": "24.4.1", + "resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-24.4.1.tgz", + "integrity": "sha512-UKhA4qv1h30XT768ccSv5NjNCX+dgfoq2qlLVmKejspPcSQTYD4SrVucgqegmYcKcmwf06wcNAa/kRd0NHWbUg==", "license": "ISC", "dependencies": { "@mapbox/jsonlint-lines-primitives": "~2.0.2", @@ -3969,9 +4020,9 @@ } }, "node_modules/@maplibre/vt-pbf": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@maplibre/vt-pbf/-/vt-pbf-4.1.0.tgz", - "integrity": "sha512-9LjFAoWtxdGRns8RK9vG3Fcw/fb3eHMxvAn2jffwn3jnVO1k49VOv6+FEza70rK7WzF8GnBiKa0K39RyfevKUw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@maplibre/vt-pbf/-/vt-pbf-4.2.0.tgz", + "integrity": "sha512-bxrk/kQUwWXZgmqYgwOCnZCMONCRi3MJMqJdza4T3E4AeR5i+VyMnaJ8iDWtWxdfEAJRtrzIOeJtxZSy5mFrFA==", "license": "MIT", "dependencies": { "@mapbox/point-geometry": "^1.1.0", @@ -4003,17 +4054,17 @@ "license": "MIT" }, "node_modules/@parcel/watcher": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", - "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.4.tgz", + "integrity": "sha512-WYa2tUVV5HiArWPB3ydlOc4R2ivq0IDrlqhMi3l7mVsFEXNcTfxYFPIHXHXIh/ca/y/V5N4E1zecyxdIBjYnkQ==", "hasInstallScript": true, "license": "MIT", "optional": true, "dependencies": { - "detect-libc": "^1.0.3", + "detect-libc": "^2.0.3", "is-glob": "^4.0.3", - "micromatch": "^4.0.5", - "node-addon-api": "^7.0.0" + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.3" }, "engines": { "node": ">= 10.0.0" @@ -4023,25 +4074,25 @@ "url": "https://opencollective.com/parcel" }, "optionalDependencies": { - "@parcel/watcher-android-arm64": "2.5.1", - "@parcel/watcher-darwin-arm64": "2.5.1", - "@parcel/watcher-darwin-x64": "2.5.1", - "@parcel/watcher-freebsd-x64": "2.5.1", - "@parcel/watcher-linux-arm-glibc": "2.5.1", - "@parcel/watcher-linux-arm-musl": "2.5.1", - "@parcel/watcher-linux-arm64-glibc": "2.5.1", - "@parcel/watcher-linux-arm64-musl": "2.5.1", - "@parcel/watcher-linux-x64-glibc": "2.5.1", - "@parcel/watcher-linux-x64-musl": "2.5.1", - "@parcel/watcher-win32-arm64": "2.5.1", - "@parcel/watcher-win32-ia32": "2.5.1", - "@parcel/watcher-win32-x64": "2.5.1" + "@parcel/watcher-android-arm64": "2.5.4", + "@parcel/watcher-darwin-arm64": "2.5.4", + "@parcel/watcher-darwin-x64": "2.5.4", + "@parcel/watcher-freebsd-x64": "2.5.4", + "@parcel/watcher-linux-arm-glibc": "2.5.4", + "@parcel/watcher-linux-arm-musl": "2.5.4", + "@parcel/watcher-linux-arm64-glibc": "2.5.4", + "@parcel/watcher-linux-arm64-musl": "2.5.4", + "@parcel/watcher-linux-x64-glibc": "2.5.4", + "@parcel/watcher-linux-x64-musl": "2.5.4", + "@parcel/watcher-win32-arm64": "2.5.4", + "@parcel/watcher-win32-ia32": "2.5.4", + "@parcel/watcher-win32-x64": "2.5.4" } }, "node_modules/@parcel/watcher-android-arm64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", - "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.4.tgz", + "integrity": "sha512-hoh0vx4v+b3BNI7Cjoy2/B0ARqcwVNrzN/n7DLq9ZB4I3lrsvhrkCViJyfTj/Qi5xM9YFiH4AmHGK6pgH1ss7g==", "cpu": [ "arm64" ], @@ -4059,9 +4110,9 @@ } }, "node_modules/@parcel/watcher-darwin-arm64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", - "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.4.tgz", + "integrity": "sha512-kphKy377pZiWpAOyTgQYPE5/XEKVMaj6VUjKT5VkNyUJlr2qZAn8gIc7CPzx+kbhvqHDT9d7EqdOqRXT6vk0zw==", "cpu": [ "arm64" ], @@ -4079,9 +4130,9 @@ } }, "node_modules/@parcel/watcher-darwin-x64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", - "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.4.tgz", + "integrity": "sha512-UKaQFhCtNJW1A9YyVz3Ju7ydf6QgrpNQfRZ35wNKUhTQ3dxJ/3MULXN5JN/0Z80V/KUBDGa3RZaKq1EQT2a2gg==", "cpu": [ "x64" ], @@ -4099,9 +4150,9 @@ } }, "node_modules/@parcel/watcher-freebsd-x64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", - "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.4.tgz", + "integrity": "sha512-Dib0Wv3Ow/m2/ttvLdeI2DBXloO7t3Z0oCp4bAb2aqyqOjKPPGrg10pMJJAQ7tt8P4V2rwYwywkDhUia/FgS+Q==", "cpu": [ "x64" ], @@ -4119,9 +4170,9 @@ } }, "node_modules/@parcel/watcher-linux-arm-glibc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", - "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.4.tgz", + "integrity": "sha512-I5Vb769pdf7Q7Sf4KNy8Pogl/URRCKu9ImMmnVKYayhynuyGYMzuI4UOWnegQNa2sGpsPSbzDsqbHNMyeyPCgw==", "cpu": [ "arm" ], @@ -4139,9 +4190,9 @@ } }, "node_modules/@parcel/watcher-linux-arm-musl": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", - "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.4.tgz", + "integrity": "sha512-kGO8RPvVrcAotV4QcWh8kZuHr9bXi9a3bSZw7kFarYR0+fGliU7hd/zevhjw8fnvIKG3J9EO5G6sXNGCSNMYPQ==", "cpu": [ "arm" ], @@ -4159,9 +4210,9 @@ } }, "node_modules/@parcel/watcher-linux-arm64-glibc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", - "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.4.tgz", + "integrity": "sha512-KU75aooXhqGFY2W5/p8DYYHt4hrjHZod8AhcGAmhzPn/etTa+lYCDB2b1sJy3sWJ8ahFVTdy+EbqSBvMx3iFlw==", "cpu": [ "arm64" ], @@ -4179,9 +4230,9 @@ } }, "node_modules/@parcel/watcher-linux-arm64-musl": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", - "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.4.tgz", + "integrity": "sha512-Qx8uNiIekVutnzbVdrgSanM+cbpDD3boB1f8vMtnuG5Zau4/bdDbXyKwIn0ToqFhIuob73bcxV9NwRm04/hzHQ==", "cpu": [ "arm64" ], @@ -4199,9 +4250,9 @@ } }, "node_modules/@parcel/watcher-linux-x64-glibc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", - "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.4.tgz", + "integrity": "sha512-UYBQvhYmgAv61LNUn24qGQdjtycFBKSK3EXr72DbJqX9aaLbtCOO8+1SkKhD/GNiJ97ExgcHBrukcYhVjrnogA==", "cpu": [ "x64" ], @@ -4219,9 +4270,9 @@ } }, "node_modules/@parcel/watcher-linux-x64-musl": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", - "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.4.tgz", + "integrity": "sha512-YoRWCVgxv8akZrMhdyVi6/TyoeeMkQ0PGGOf2E4omODrvd1wxniXP+DBynKoHryStks7l+fDAMUBRzqNHrVOpg==", "cpu": [ "x64" ], @@ -4239,9 +4290,9 @@ } }, "node_modules/@parcel/watcher-win32-arm64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", - "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.4.tgz", + "integrity": "sha512-iby+D/YNXWkiQNYcIhg8P5hSjzXEHaQrk2SLrWOUD7VeC4Ohu0WQvmV+HDJokZVJ2UjJ4AGXW3bx7Lls9Ln4TQ==", "cpu": [ "arm64" ], @@ -4259,9 +4310,9 @@ } }, "node_modules/@parcel/watcher-win32-ia32": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", - "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.4.tgz", + "integrity": "sha512-vQN+KIReG0a2ZDpVv8cgddlf67J8hk1WfZMMP7sMeZmJRSmEax5xNDNWKdgqSe2brOKTQQAs3aCCUal2qBHAyg==", "cpu": [ "ia32" ], @@ -4279,9 +4330,9 @@ } }, "node_modules/@parcel/watcher-win32-x64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", - "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.4.tgz", + "integrity": "sha512-3A6efb6BOKwyw7yk9ro2vus2YTt2nvcd56AuzxdMiVOxL9umDyN5PKkKfZ/gZ9row41SjVmTVQNWQhaRRGpOKw==", "cpu": [ "x64" ], @@ -4407,9 +4458,9 @@ "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", - "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==", + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.3.tgz", + "integrity": "sha512-qyX8+93kK/7R5BEXPC2PjUt0+fS/VO2BVHjEHyIEWiYn88rcRBHmdLgoJjktBltgAf+NY7RfCGB1SoyKS/p9kg==", "cpu": [ "arm" ], @@ -4420,9 +4471,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz", - "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==", + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.55.3.tgz", + "integrity": "sha512-6sHrL42bjt5dHQzJ12Q4vMKfN+kUnZ0atHHnv4V0Wd9JMTk7FDzSY35+7qbz3ypQYMBPANbpGK7JpnWNnhGt8g==", "cpu": [ "arm64" ], @@ -4433,9 +4484,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz", - "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==", + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.55.3.tgz", + "integrity": "sha512-1ht2SpGIjEl2igJ9AbNpPIKzb1B5goXOcmtD0RFxnwNuMxqkR6AUaaErZz+4o+FKmzxcSNBOLrzsICZVNYa1Rw==", "cpu": [ "arm64" ], @@ -4446,9 +4497,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz", - "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==", + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.55.3.tgz", + "integrity": "sha512-FYZ4iVunXxtT+CZqQoPVwPhH7549e/Gy7PIRRtq4t5f/vt54pX6eG9ebttRH6QSH7r/zxAFA4EZGlQ0h0FvXiA==", "cpu": [ "x64" ], @@ -4459,9 +4510,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz", - "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==", + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.55.3.tgz", + "integrity": "sha512-M/mwDCJ4wLsIgyxv2Lj7Len+UMHd4zAXu4GQ2UaCdksStglWhP61U3uowkaYBQBhVoNpwx5Hputo8eSqM7K82Q==", "cpu": [ "arm64" ], @@ -4472,9 +4523,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz", - "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==", + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.55.3.tgz", + "integrity": "sha512-5jZT2c7jBCrMegKYTYTpni8mg8y3uY8gzeq2ndFOANwNuC/xJbVAoGKR9LhMDA0H3nIhvaqUoBEuJoICBudFrA==", "cpu": [ "x64" ], @@ -4485,9 +4536,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz", - "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==", + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.55.3.tgz", + "integrity": "sha512-YeGUhkN1oA+iSPzzhEjVPS29YbViOr8s4lSsFaZKLHswgqP911xx25fPOyE9+khmN6W4VeM0aevbDp4kkEoHiA==", "cpu": [ "arm" ], @@ -4498,9 +4549,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz", - "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==", + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.55.3.tgz", + "integrity": "sha512-eo0iOIOvcAlWB3Z3eh8pVM8hZ0oVkK3AjEM9nSrkSug2l15qHzF3TOwT0747omI6+CJJvl7drwZepT+re6Fy/w==", "cpu": [ "arm" ], @@ -4511,9 +4562,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", - "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.55.3.tgz", + "integrity": "sha512-DJay3ep76bKUDImmn//W5SvpjRN5LmK/ntWyeJs/dcnwiiHESd3N4uteK9FDLf0S0W8E6Y0sVRXpOCoQclQqNg==", "cpu": [ "arm64" ], @@ -4524,9 +4575,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz", - "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==", + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.55.3.tgz", + "integrity": "sha512-BKKWQkY2WgJ5MC/ayvIJTHjy0JUGb5efaHCUiG/39sSUvAYRBaO3+/EK0AZT1RF3pSj86O24GLLik9mAYu0IJg==", "cpu": [ "arm64" ], @@ -4537,9 +4588,22 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz", - "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==", + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.55.3.tgz", + "integrity": "sha512-Q9nVlWtKAG7ISW80OiZGxTr6rYtyDSkauHUtvkQI6TNOJjFvpj4gcH+KaJihqYInnAzEEUetPQubRwHef4exVg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.55.3.tgz", + "integrity": "sha512-2H5LmhzrpC4fFRNwknzmmTvvyJPHwESoJgyReXeFoYYuIDfBhP29TEXOkCJE/KxHi27mj7wDUClNq78ue3QEBQ==", "cpu": [ "loong64" ], @@ -4550,9 +4614,22 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz", - "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==", + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.55.3.tgz", + "integrity": "sha512-9S542V0ie9LCTznPYlvaeySwBeIEa7rDBgLHKZ5S9DBgcqdJYburabm8TqiqG6mrdTzfV5uttQRHcbKff9lWtA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.55.3.tgz", + "integrity": "sha512-ukxw+YH3XXpcezLgbJeasgxyTbdpnNAkrIlFGDl7t+pgCxZ89/6n1a+MxlY7CegU+nDgrgdqDelPRNQ/47zs0g==", "cpu": [ "ppc64" ], @@ -4563,9 +4640,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz", - "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==", + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.55.3.tgz", + "integrity": "sha512-Iauw9UsTTvlF++FhghFJjqYxyXdggXsOqGpFBylaRopVpcbfyIIsNvkf9oGwfgIcf57z3m8+/oSYTo6HutBFNw==", "cpu": [ "riscv64" ], @@ -4576,9 +4653,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz", - "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==", + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.55.3.tgz", + "integrity": "sha512-3OqKAHSEQXKdq9mQ4eajqUgNIK27VZPW3I26EP8miIzuKzCJ3aW3oEn2pzF+4/Hj/Moc0YDsOtBgT5bZ56/vcA==", "cpu": [ "riscv64" ], @@ -4589,9 +4666,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz", - "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==", + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.55.3.tgz", + "integrity": "sha512-0CM8dSVzVIaqMcXIFej8zZrSFLnGrAE8qlNbbHfTw1EEPnFTg1U1ekI0JdzjPyzSfUsHWtodilQQG/RA55berA==", "cpu": [ "s390x" ], @@ -4602,9 +4679,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz", - "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==", + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.55.3.tgz", + "integrity": "sha512-+fgJE12FZMIgBaKIAGd45rxf+5ftcycANJRWk8Vz0NnMTM5rADPGuRFTYar+Mqs560xuART7XsX2lSACa1iOmQ==", "cpu": [ "x64" ], @@ -4615,9 +4692,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz", - "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==", + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.55.3.tgz", + "integrity": "sha512-tMD7NnbAolWPzQlJQJjVFh/fNH3K/KnA7K8gv2dJWCwwnaK6DFCYST1QXYWfu5V0cDwarWC8Sf/cfMHniNq21A==", "cpu": [ "x64" ], @@ -4627,10 +4704,23 @@ "linux" ] }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.55.3.tgz", + "integrity": "sha512-u5KsqxOxjEeIbn7bUK1MPM34jrnPwjeqgyin4/N6e/KzXKfpE9Mi0nCxcQjaM9lLmPcHmn/xx1yOjgTMtu1jWQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz", - "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==", + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.55.3.tgz", + "integrity": "sha512-vo54aXwjpTtsAnb3ca7Yxs9t2INZg7QdXN/7yaoG7nPGbOBXYXQY41Km+S1Ov26vzOAzLcAjmMdjyEqS1JkVhw==", "cpu": [ "arm64" ], @@ -4641,9 +4731,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz", - "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==", + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.55.3.tgz", + "integrity": "sha512-HI+PIVZ+m+9AgpnY3pt6rinUdRYrGHvmVdsNQ4odNqQ/eRF78DVpMR7mOq7nW06QxpczibwBmeQzB68wJ+4W4A==", "cpu": [ "arm64" ], @@ -4654,9 +4744,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz", - "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==", + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.55.3.tgz", + "integrity": "sha512-vRByotbdMo3Wdi+8oC2nVxtc3RkkFKrGaok+a62AT8lz/YBuQjaVYAS5Zcs3tPzW43Vsf9J0wehJbUY5xRSekA==", "cpu": [ "ia32" ], @@ -4667,9 +4757,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz", - "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==", + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.55.3.tgz", + "integrity": "sha512-POZHq7UeuzMJljC5NjKi8vKMFN6/5EOqcX1yGntNLp7rUTpBAXQ1hW8kWPFxYLv07QMcNM75xqVLGPWQq6TKFA==", "cpu": [ "x64" ], @@ -4680,9 +4770,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz", - "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==", + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.55.3.tgz", + "integrity": "sha512-aPFONczE4fUFKNXszdvnd2GqKEYQdV5oEsIbKPujJmWlCI9zEsv1Otig8RKK+X9bed9gFUN6LAeN4ZcNuu4zjg==", "cpu": [ "x64" ], @@ -4770,9 +4860,9 @@ "license": "MIT" }, "node_modules/@testing-library/react": { - "version": "16.3.0", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.0.tgz", - "integrity": "sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==", + "version": "16.3.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", + "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.12.5" @@ -4966,9 +5056,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.0.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.1.tgz", - "integrity": "sha512-czWPzKIAXucn9PtsttxmumiQ9N0ok9FrBwgRWrwmVLlp86BrMExzvXRLFYRJ+Ex3g6yqj+KuaxfX1JTgV2lpfg==", + "version": "25.0.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.9.tgz", + "integrity": "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw==", "license": "MIT", "dependencies": { "undici-types": "~7.16.0" @@ -5256,23 +5346,23 @@ } }, "node_modules/@volar/language-core": { - "version": "2.4.26", - "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.26.tgz", - "integrity": "sha512-hH0SMitMxnB43OZpyF1IFPS9bgb2I3bpCh76m2WEK7BE0A0EzpYsRp0CCH2xNKshr7kacU5TQBLYn4zj7CG60A==", + "version": "2.4.27", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.27.tgz", + "integrity": "sha512-DjmjBWZ4tJKxfNC1F6HyYERNHPYS7L7OPFyCrestykNdUZMFYzI9WTyvwPcaNaHlrEUwESHYsfEw3isInncZxQ==", "license": "MIT", "dependencies": { - "@volar/source-map": "2.4.26" + "@volar/source-map": "2.4.27" } }, "node_modules/@volar/language-server": { - "version": "2.4.26", - "resolved": "https://registry.npmjs.org/@volar/language-server/-/language-server-2.4.26.tgz", - "integrity": "sha512-Xsyu+VDgM8TyVkQfBz2aIViSEOgH2un0gIJlp0M8rssDDLCqr4ssQzwHOyPf7sT7UIjrlAMnJvRkC/u0mmgtYw==", + "version": "2.4.27", + "resolved": "https://registry.npmjs.org/@volar/language-server/-/language-server-2.4.27.tgz", + "integrity": "sha512-SymGNkErcHg8GjiG65iQN8sLkhqu1pwKhFySmxeBuYq5xFYagKBW36eiNITXQTdvT0tutI1GXcXdq/FdE/IyjA==", "license": "MIT", "dependencies": { - "@volar/language-core": "2.4.26", - "@volar/language-service": "2.4.26", - "@volar/typescript": "2.4.26", + "@volar/language-core": "2.4.27", + "@volar/language-service": "2.4.27", + "@volar/typescript": "2.4.27", "path-browserify": "^1.0.1", "request-light": "^0.7.0", "vscode-languageserver": "^9.0.1", @@ -5282,30 +5372,30 @@ } }, "node_modules/@volar/language-service": { - "version": "2.4.26", - "resolved": "https://registry.npmjs.org/@volar/language-service/-/language-service-2.4.26.tgz", - "integrity": "sha512-ZBPRR1ytXttSV5X4VPvEQR/glxs+7/4IOJIBCOW3/EJk4z77R4mF2y4wM3fNgOXXZT5h16j3sC5w+LGNkz2VlA==", + "version": "2.4.27", + "resolved": "https://registry.npmjs.org/@volar/language-service/-/language-service-2.4.27.tgz", + "integrity": "sha512-SxKZ8yLhpWa7Y5e/RDxtNfm7j7xsXp/uf2urijXEffRNpPSmVdfzQrFFy5d7l8PNpZy+bHg+yakmqBPjQN+MOw==", "license": "MIT", "dependencies": { - "@volar/language-core": "2.4.26", + "@volar/language-core": "2.4.27", "vscode-languageserver-protocol": "^3.17.5", "vscode-languageserver-textdocument": "^1.0.11", "vscode-uri": "^3.0.8" } }, "node_modules/@volar/source-map": { - "version": "2.4.26", - "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.26.tgz", - "integrity": "sha512-JJw0Tt/kSFsIRmgTQF4JSt81AUSI1aEye5Zl65EeZ8H35JHnTvFGmpDOBn5iOxd48fyGE+ZvZBp5FcgAy/1Qhw==", + "version": "2.4.27", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.27.tgz", + "integrity": "sha512-ynlcBReMgOZj2i6po+qVswtDUeeBRCTgDurjMGShbm8WYZgJ0PA4RmtebBJ0BCYol1qPv3GQF6jK7C9qoVc7lg==", "license": "MIT" }, "node_modules/@volar/typescript": { - "version": "2.4.26", - "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.26.tgz", - "integrity": "sha512-N87ecLD48Sp6zV9zID/5yuS1+5foj0DfuYGdQ6KHj/IbKvyKv1zNX6VCmnKYwtmHadEO6mFc2EKISiu3RDPAvA==", + "version": "2.4.27", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.27.tgz", + "integrity": "sha512-eWaYCcl/uAPInSK2Lze6IqVWaBu/itVqR5InXcHXFyles4zO++Mglt3oxdgj75BDcv1Knr9Y93nowS8U3wqhxg==", "license": "MIT", "dependencies": { - "@volar/language-core": "2.4.26", + "@volar/language-core": "2.4.27", "path-browserify": "^1.0.1", "vscode-uri": "^3.0.8" } @@ -5330,14 +5420,14 @@ "license": "MIT" }, "node_modules/@vue/compiler-core": { - "version": "3.5.25", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.25.tgz", - "integrity": "sha512-vay5/oQJdsNHmliWoZfHPoVZZRmnSWhug0BYT34njkYTPqClh3DNWLkZNJBVSjsNMrg0CCrBfoKkjZQPM/QVUw==", + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.27.tgz", + "integrity": "sha512-gnSBQjZA+//qDZen+6a2EdHqJ68Z7uybrMf3SPjEGgG4dicklwDVmMC1AeIHxtLVPT7sn6sH1KOO+tS6gwOUeQ==", "license": "MIT", "dependencies": { "@babel/parser": "^7.28.5", - "@vue/shared": "3.5.25", - "entities": "^4.5.0", + "@vue/shared": "3.5.27", + "entities": "^7.0.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } @@ -5349,26 +5439,26 @@ "license": "MIT" }, "node_modules/@vue/compiler-dom": { - "version": "3.5.25", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.25.tgz", - "integrity": "sha512-4We0OAcMZsKgYoGlMjzYvaoErltdFI2/25wqanuTu+S4gismOTRTBPi4IASOjxWdzIwrYSjnqONfKvuqkXzE2Q==", + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.27.tgz", + "integrity": "sha512-oAFea8dZgCtVVVTEC7fv3T5CbZW9BxpFzGGxC79xakTr6ooeEqmRuvQydIiDAkglZEAd09LgVf1RoDnL54fu5w==", "license": "MIT", "dependencies": { - "@vue/compiler-core": "3.5.25", - "@vue/shared": "3.5.25" + "@vue/compiler-core": "3.5.27", + "@vue/shared": "3.5.27" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.5.25", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.25.tgz", - "integrity": "sha512-PUgKp2rn8fFsI++lF2sO7gwO2d9Yj57Utr5yEsDf3GNaQcowCLKL7sf+LvVFvtJDXUp/03+dC6f2+LCv5aK1ag==", + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.27.tgz", + "integrity": "sha512-sHZu9QyDPeDmN/MRoshhggVOWE5WlGFStKFwu8G52swATgSny27hJRWteKDSUUzUH+wp+bmeNbhJnEAel/auUQ==", "license": "MIT", "dependencies": { "@babel/parser": "^7.28.5", - "@vue/compiler-core": "3.5.25", - "@vue/compiler-dom": "3.5.25", - "@vue/compiler-ssr": "3.5.25", - "@vue/shared": "3.5.25", + "@vue/compiler-core": "3.5.27", + "@vue/compiler-dom": "3.5.27", + "@vue/compiler-ssr": "3.5.27", + "@vue/shared": "3.5.27", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.6", @@ -5382,13 +5472,13 @@ "license": "MIT" }, "node_modules/@vue/compiler-ssr": { - "version": "3.5.25", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.25.tgz", - "integrity": "sha512-ritPSKLBcParnsKYi+GNtbdbrIE1mtuFEJ4U1sWeuOMlIziK5GtOL85t5RhsNy4uWIXPgk+OUdpnXiTdzn8o3A==", + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.27.tgz", + "integrity": "sha512-Sj7h+JHt512fV1cTxKlYhg7qxBvack+BGncSpH+8vnN+KN95iPIcqB5rsbblX40XorP+ilO7VIKlkuu3Xq2vjw==", "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.25", - "@vue/shared": "3.5.25" + "@vue/compiler-dom": "3.5.27", + "@vue/shared": "3.5.27" } }, "node_modules/@vue/devtools-api": { @@ -5398,38 +5488,30 @@ "license": "MIT" }, "node_modules/@vue/language-core": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.1.8.tgz", - "integrity": "sha512-PfwAW7BLopqaJbneChNL6cUOTL3GL+0l8paYP5shhgY5toBNidWnMXWM+qDwL7MC9+zDtzCF2enT8r6VPu64iw==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.2.2.tgz", + "integrity": "sha512-5DAuhxsxBN9kbriklh3Q5AMaJhyOCNiQJvCskN9/30XOpdLiqZU9Q+WvjArP17ubdGEyZtBzlIeG5nIjEbNOrQ==", "license": "MIT", "dependencies": { - "@volar/language-core": "2.4.26", + "@volar/language-core": "2.4.27", "@vue/compiler-dom": "^3.5.0", "@vue/shared": "^3.5.0", "alien-signals": "^3.0.0", "muggle-string": "^0.4.1", "path-browserify": "^1.0.1", "picomatch": "^4.0.2" - }, - "peerDependencies": { - "typescript": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } } }, "node_modules/@vue/language-server": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/@vue/language-server/-/language-server-3.1.8.tgz", - "integrity": "sha512-Tw7Dw4qaDfW0s01gAdFIxX8ZcllrLMI1XXZRx3QI9iIcpJVJGevW6+TYhg/xvpT2NKGwkImzM9UoXZwWlnNTEg==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@vue/language-server/-/language-server-3.2.2.tgz", + "integrity": "sha512-E9QUhvqzVlAZVxoGcmUjWdZgEyb0dr+DPW7TtUecGOAJIIyEedzPDW6sCZflW10MdDm5y7omdqPs8zIvr7tyPQ==", "license": "MIT", "dependencies": { - "@volar/language-server": "2.4.26", - "@vue/language-core": "3.1.8", - "@vue/language-service": "3.1.8", - "@vue/typescript-plugin": "3.1.8", + "@volar/language-server": "2.4.27", + "@vue/language-core": "3.2.2", + "@vue/language-service": "3.2.2", + "@vue/typescript-plugin": "3.2.2", "vscode-uri": "^3.0.8" }, "bin": { @@ -5440,13 +5522,13 @@ } }, "node_modules/@vue/language-service": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/@vue/language-service/-/language-service-3.1.8.tgz", - "integrity": "sha512-/dcZn/hSOlv4njHqvti+lH/txyC2BPs60Ih96Z2RsJ4iCu4NBCe9wYxqLhdtYSBZaDHxDcI2O7cI4fREO7brIw==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@vue/language-service/-/language-service-3.2.2.tgz", + "integrity": "sha512-sv6mug6j1GsJSIOsYS+3x2Lq69VS7BXUUP1qD6tBxr7WlENwU8rXSk3a/FJAVw807AMQIPi+U1LkqdxHQkCzjw==", "license": "MIT", "dependencies": { - "@volar/language-service": "2.4.26", - "@vue/language-core": "3.1.8", + "@volar/language-service": "2.4.27", + "@vue/language-core": "3.2.2", "@vue/shared": "^3.5.0", "path-browserify": "^1.0.1", "volar-service-css": "0.0.68", @@ -5461,53 +5543,53 @@ } }, "node_modules/@vue/reactivity": { - "version": "3.5.25", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.25.tgz", - "integrity": "sha512-5xfAypCQepv4Jog1U4zn8cZIcbKKFka3AgWHEFQeK65OW+Ys4XybP6z2kKgws4YB43KGpqp5D/K3go2UPPunLA==", + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.27.tgz", + "integrity": "sha512-vvorxn2KXfJ0nBEnj4GYshSgsyMNFnIQah/wczXlsNXt+ijhugmW+PpJ2cNPe4V6jpnBcs0MhCODKllWG+nvoQ==", "license": "MIT", "dependencies": { - "@vue/shared": "3.5.25" + "@vue/shared": "3.5.27" } }, "node_modules/@vue/runtime-core": { - "version": "3.5.25", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.25.tgz", - "integrity": "sha512-Z751v203YWwYzy460bzsYQISDfPjHTl+6Zzwo/a3CsAf+0ccEjQ8c+0CdX1WsumRTHeywvyUFtW6KvNukT/smA==", + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.27.tgz", + "integrity": "sha512-fxVuX/fzgzeMPn/CLQecWeDIFNt3gQVhxM0rW02Tvp/YmZfXQgcTXlakq7IMutuZ/+Ogbn+K0oct9J3JZfyk3A==", "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.25", - "@vue/shared": "3.5.25" + "@vue/reactivity": "3.5.27", + "@vue/shared": "3.5.27" } }, "node_modules/@vue/runtime-dom": { - "version": "3.5.25", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.25.tgz", - "integrity": "sha512-a4WrkYFbb19i9pjkz38zJBg8wa/rboNERq3+hRRb0dHiJh13c+6kAbgqCPfMaJ2gg4weWD3APZswASOfmKwamA==", + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.27.tgz", + "integrity": "sha512-/QnLslQgYqSJ5aUmb5F0z0caZPGHRB8LEAQ1s81vHFM5CBfnun63rxhvE/scVb/j3TbBuoZwkJyiLCkBluMpeg==", "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.25", - "@vue/runtime-core": "3.5.25", - "@vue/shared": "3.5.25", - "csstype": "^3.1.3" + "@vue/reactivity": "3.5.27", + "@vue/runtime-core": "3.5.27", + "@vue/shared": "3.5.27", + "csstype": "^3.2.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.5.25", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.25.tgz", - "integrity": "sha512-UJaXR54vMG61i8XNIzTSf2Q7MOqZHpp8+x3XLGtE3+fL+nQd+k7O5+X3D/uWrnQXOdMw5VPih+Uremcw+u1woQ==", + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.27.tgz", + "integrity": "sha512-qOz/5thjeP1vAFc4+BY3Nr6wxyLhpeQgAE/8dDtKo6a6xdk+L4W46HDZgNmLOBUDEkFXV3G7pRiUqxjX0/2zWA==", "license": "MIT", "dependencies": { - "@vue/compiler-ssr": "3.5.25", - "@vue/shared": "3.5.25" + "@vue/compiler-ssr": "3.5.27", + "@vue/shared": "3.5.27" }, "peerDependencies": { - "vue": "3.5.25" + "vue": "3.5.27" } }, "node_modules/@vue/shared": { - "version": "3.5.25", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.25.tgz", - "integrity": "sha512-AbOPdQQnAnzs58H2FrrDxYj/TJfmeS2jdfEEhgiKINy+bnOANmVizIEgq1r+C5zsbs6l1CCQxtcj71rwNQ4jWg==", + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.27.tgz", + "integrity": "sha512-dXr/3CgqXsJkZ0n9F3I4elY8wM9jMJpP3pvRG52r6m0tu/MsAFIe6JpXVGeNMd/D9F4hQynWT8Rfuj0bdm9kFQ==", "license": "MIT" }, "node_modules/@vue/test-utils": { @@ -5521,28 +5603,29 @@ } }, "node_modules/@vue/typescript-plugin": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/@vue/typescript-plugin/-/typescript-plugin-3.1.8.tgz", - "integrity": "sha512-pGXOtxzILWJm0YVTiG9JTzge9a3bX6462Aif1FVYPyy+B/CbBluEDSse7r52Vs5tceaf+rjfaE/q4B9aGNNUzw==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@vue/typescript-plugin/-/typescript-plugin-3.2.2.tgz", + "integrity": "sha512-7mQeWWMAVeloiN0y2ZCX/kSiGbGjvI4TEc3f9at4qVdUuhMnRZ4JBE5TQFnZEybGoHdEfj86fgW+ve2Jg7z99g==", "license": "MIT", "dependencies": { - "@volar/typescript": "2.4.26", - "@vue/language-core": "3.1.8", + "@volar/typescript": "2.4.27", + "@vue/language-core": "3.2.2", "@vue/shared": "^3.5.0", - "path-browserify": "^1.0.1" + "path-browserify": "^1.0.1", + "vue-component-meta": "3.2.2" } }, "node_modules/@vuetify/loader-shared": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@vuetify/loader-shared/-/loader-shared-2.1.1.tgz", - "integrity": "sha512-jSZTzTYaoiv8iwonFCVZQ0YYX/M+Uyl4ng+C4egMJT0Hcmh9gIxJL89qfZICDeo3g0IhqrvipW2FFKKRDMtVcA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@vuetify/loader-shared/-/loader-shared-2.1.2.tgz", + "integrity": "sha512-X+1jBLmXHkpQEnC0vyOb4rtX2QSkBiFhaFXz8yhQqN2A4vQ6k2nChxN4Ol7VAY5KoqMdFoRMnmNdp/1qYXDQig==", "license": "MIT", "dependencies": { "upath": "^2.0.1" }, "peerDependencies": { "vue": "^3.0.0", - "vuetify": "^3.0.0" + "vuetify": ">=3" } }, "node_modules/@vvo/tzdb": { @@ -5894,9 +5977,9 @@ } }, "node_modules/alien-signals": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-3.1.1.tgz", - "integrity": "sha512-ogkIWbVrLwKtHY6oOAXaYkAxP+cTH7V5FZ5+Tm4NZFd8VDZ6uNMDrfzqctTZ42eTMCSR3ne3otpcxmqSnFfPYA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-3.1.2.tgz", + "integrity": "sha512-d9dYqZTS90WLiU0I5c6DHj/HcKkF8ZyGN3G5x8wSbslulz70KOxaqCT0hQCo9KOyhVqzqGojvNdJXoTumZOtcw==", "license": "MIT" }, "node_modules/ansi-escapes": { @@ -6124,9 +6207,9 @@ } }, "node_modules/ast-v8-to-istanbul": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.8.tgz", - "integrity": "sha512-szgSZqUxI5T8mLKvS7WTjF9is+MVbOeLADU73IseOcrqhxr/VAvy6wfoVE39KnKzA7JRhjF5eUagNlHwvZPlKQ==", + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.10.tgz", + "integrity": "sha512-p4K7vMz2ZSk3wN8l5o3y2bJAoZXT3VuJI5OLTATY/01CYWumWvwkUw0SqDBnNq6IiTO3qDa1eSQDibAV8g7XOQ==", "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.31", @@ -6171,9 +6254,9 @@ } }, "node_modules/autoprefixer": { - "version": "10.4.22", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.22.tgz", - "integrity": "sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg==", + "version": "10.4.23", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.23.tgz", + "integrity": "sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==", "funding": [ { "type": "opencollective", @@ -6190,10 +6273,9 @@ ], "license": "MIT", "dependencies": { - "browserslist": "^4.27.0", - "caniuse-lite": "^1.0.30001754", + "browserslist": "^4.28.1", + "caniuse-lite": "^1.0.30001760", "fraction.js": "^5.3.4", - "normalize-range": "^0.1.2", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, @@ -6341,9 +6423,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.9.6", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.6.tgz", - "integrity": "sha512-v9BVVpOTLB59C9E7aSnmIF8h7qRsFpx+A2nugVMTszEOMcfjlZMsXRm4LF23I3Z9AJxc8ANpIvzbzONoX9VJlg==", + "version": "2.9.17", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.17.tgz", + "integrity": "sha512-agD0MgJFUP/4nvjqzIB29zRPUuCF7Ge6mEv9s8dHrtYD7QWXRcx75rOADE/d5ah1NI+0vkDl0yorDd5U852IQQ==", "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.js" @@ -6533,9 +6615,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001760", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001760.tgz", - "integrity": "sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==", + "version": "1.0.30001765", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001765.tgz", + "integrity": "sha512-LWcNtSyZrakjECqmpP4qdg0MMGdN368D7X8XvvAqOcqMv0RxnlqVKZl2V6/mBR68oYMxOZPLw/gO7DuisMHUvQ==", "funding": [ { "type": "opencollective", @@ -6603,9 +6685,9 @@ } }, "node_modules/check-error": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", - "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", "license": "MIT", "engines": { "node": ">= 16" @@ -6956,9 +7038,9 @@ } }, "node_modules/css-declaration-sorter": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.3.0.tgz", - "integrity": "sha512-LQF6N/3vkAMYF4xoHLJfG718HRJh34Z8BnNhd6bosOMIVjMlhuZK5++oZa3uYAgrI5+7x2o27gUqTR2U/KjUOQ==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.3.1.tgz", + "integrity": "sha512-gz6x+KkgNCjxq3Var03pRYLhyNfwhkKF1g/yoLgDNtFvVu0/fOLV9C8fFEZRjACp/XQLumjAYo7JVjzH3wLbxA==", "license": "ISC", "engines": { "node": "^14 || ^16 || >=18" @@ -7117,9 +7199,9 @@ "license": "MIT" }, "node_modules/cssdb": { - "version": "8.5.2", - "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-8.5.2.tgz", - "integrity": "sha512-Pmoj9RmD8RIoIzA2EQWO4D4RMeDts0tgAH0VXdlNdxjuBGI3a9wMOIcUwaPNmD4r2qtIa06gqkIf7sECl+cBCg==", + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-8.7.0.tgz", + "integrity": "sha512-UxiWVpV953ENHqAKjKRPZHNDfRo3uOymvO5Ef7MFCWlenaohkYj7PTO7WCBdjZm8z/aDZd6rXyUIlwZ0AjyFSg==", "funding": [ { "type": "opencollective", @@ -7457,16 +7539,13 @@ } }, "node_modules/detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "license": "Apache-2.0", "optional": true, - "bin": { - "detect-libc": "bin/detect-libc.js" - }, "engines": { - "node": ">=0.10" + "node": ">=8" } }, "node_modules/doctrine": { @@ -7501,6 +7580,18 @@ "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" } }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/domelementtype": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", @@ -7703,9 +7794,9 @@ } }, "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -7745,9 +7836,9 @@ } }, "node_modules/es-abstract": { - "version": "1.24.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", - "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", + "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", "license": "MIT", "dependencies": { "array-buffer-byte-length": "^1.0.2", @@ -7893,9 +7984,9 @@ } }, "node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", "hasInstallScript": true, "license": "MIT", "bin": { @@ -7905,32 +7996,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" } }, "node_modules/escalade": { @@ -7955,9 +8046,9 @@ } }, "node_modules/eslint": { - "version": "9.39.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz", - "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", @@ -7966,7 +8057,7 @@ "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.39.1", + "@eslint/js": "9.39.2", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -8139,22 +8230,10 @@ "node": ">=16.0.0" } }, - "node_modules/eslint-plugin-html/node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/eslint-plugin-html/node_modules/htmlparser2": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz", - "integrity": "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", + "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", "funding": [ "https://github.com/fb55/htmlparser2?sponsor=1", { @@ -8166,8 +8245,8 @@ "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", - "domutils": "^3.2.1", - "entities": "^6.0.0" + "domutils": "^3.2.2", + "entities": "^7.0.1" } }, "node_modules/eslint-plugin-import": { @@ -8233,13 +8312,13 @@ } }, "node_modules/eslint-plugin-prettier": { - "version": "5.5.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.4.tgz", - "integrity": "sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==", + "version": "5.5.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.5.tgz", + "integrity": "sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==", "license": "MIT", "dependencies": { - "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.11.7" + "prettier-linter-helpers": "^1.0.1", + "synckit": "^0.11.12" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -8263,9 +8342,9 @@ } }, "node_modules/eslint-plugin-vue": { - "version": "10.6.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-10.6.2.tgz", - "integrity": "sha512-nA5yUs/B1KmKzvC42fyD0+l9Yd+LtEpVhWRbXuDj0e+ZURcTtyRbMDWUeJmTAh2wC6jC83raS63anNM2YT3NPw==", + "version": "10.7.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-10.7.0.tgz", + "integrity": "sha512-r2XFCK4qlo1sxEoAMIoTTX0PZAdla0JJDt1fmYiworZUX67WeEGqm+JbyAg3M+pGiJ5U6Mp5WQbontXWtIW7TA==", "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", @@ -8455,9 +8534,9 @@ } }, "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" @@ -9358,6 +9437,18 @@ "entities": "^4.4.0" } }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", @@ -10845,9 +10936,9 @@ } }, "node_modules/maplibre-gl": { - "version": "5.14.0", - "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-5.14.0.tgz", - "integrity": "sha512-O2ok6N/bQ9NA9nJ22r/PRQQYkUe9JwfDMjBPkQ+8OwsVH4TpA5skIAM2wc0k+rni5lVbAVONVyBvgi1rF2vEPA==", + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-5.16.0.tgz", + "integrity": "sha512-/VDY89nr4jgLJyzmhy325cG6VUI02WkZ/UfVuDbG/piXzo6ODnM+omDFIwWY8tsEsBG26DNDmNMn3Y2ikHsBiA==", "license": "BSD-3-Clause", "dependencies": { "@mapbox/geojson-rewind": "^0.5.2", @@ -10857,9 +10948,9 @@ "@mapbox/unitbezier": "^0.0.1", "@mapbox/vector-tile": "^2.0.4", "@mapbox/whoots-js": "^3.1.0", - "@maplibre/maplibre-gl-style-spec": "^24.3.1", + "@maplibre/maplibre-gl-style-spec": "^24.4.1", "@maplibre/mlt": "^1.1.2", - "@maplibre/vt-pbf": "^4.1.0", + "@maplibre/vt-pbf": "^4.2.0", "@types/geojson": "^7946.0.16", "@types/geojson-vt": "3.2.5", "@types/supercluster": "^7.1.3", @@ -10994,9 +11085,9 @@ } }, "node_modules/mini-css-extract-plugin": { - "version": "2.9.4", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.4.tgz", - "integrity": "sha512-ZWYT7ln73Hptxqxk2DxPU9MmapXRhxkJD6tkSR04dnQxm8BGu2hzgKLugK5yySD97u/8yy7Ma7E76k9ZdvtjkQ==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.10.0.tgz", + "integrity": "sha512-540P2c5dYnJlyJxTaSloliZexv8rji6rY8FhQN+WF/82iHQfA23j/xtJx97L+mXOML27EqksSek/g4eK7jaL3g==", "license": "MIT", "dependencies": { "schema-utils": "^4.0.0", @@ -11170,15 +11261,6 @@ "node": ">=0.10.0" } }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", @@ -12824,9 +12906,9 @@ } }, "node_modules/postcss-preset-env": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-10.5.0.tgz", - "integrity": "sha512-xgxFQPAPxeWmsgy8cR7GM1PGAL/smA5E9qU7K//D4vucS01es3M0fDujhDJn3kY8Ip7/vVYcecbe1yY+vBo3qQ==", + "version": "10.6.1", + "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-10.6.1.tgz", + "integrity": "sha512-yrk74d9EvY+W7+lO9Aj1QmjWY9q5NsKjK2V9drkOPZB/X6KZ0B3igKsHUYakb7oYVhnioWypQX3xGuePf89f3g==", "funding": [ { "type": "github", @@ -12864,25 +12946,27 @@ "@csstools/postcss-media-minmax": "^2.0.9", "@csstools/postcss-media-queries-aspect-ratio-number-values": "^3.0.5", "@csstools/postcss-nested-calc": "^4.0.0", - "@csstools/postcss-normalize-display-values": "^4.0.0", + "@csstools/postcss-normalize-display-values": "^4.0.1", "@csstools/postcss-oklab-function": "^4.0.12", "@csstools/postcss-position-area-property": "^1.0.0", "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/postcss-property-rule-prelude-list": "^1.0.0", "@csstools/postcss-random-function": "^2.0.1", "@csstools/postcss-relative-color-syntax": "^3.0.12", "@csstools/postcss-scope-pseudo-class": "^4.0.1", "@csstools/postcss-sign-functions": "^1.1.4", "@csstools/postcss-stepped-value-functions": "^4.0.9", + "@csstools/postcss-syntax-descriptor-syntax-production": "^1.0.1", "@csstools/postcss-system-ui-font-family": "^1.0.0", "@csstools/postcss-text-decoration-shorthand": "^4.0.3", "@csstools/postcss-trigonometric-functions": "^4.0.9", "@csstools/postcss-unset-value": "^4.0.0", - "autoprefixer": "^10.4.22", - "browserslist": "^4.28.0", + "autoprefixer": "^10.4.23", + "browserslist": "^4.28.1", "css-blank-pseudo": "^7.0.1", "css-has-pseudo": "^7.0.3", "css-prefers-color-scheme": "^10.0.0", - "cssdb": "^8.5.2", + "cssdb": "^8.6.0", "postcss-attribute-case-insensitive": "^7.0.1", "postcss-clamp": "^4.1.0", "postcss-color-functional-notation": "^7.0.12", @@ -13143,9 +13227,9 @@ } }, "node_modules/prettier": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz", - "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.0.tgz", + "integrity": "sha512-yEPsovQfpxYfgWNhCfECjG5AQaO+K3dp6XERmOepyPDVqcJm+bjyCVO3pmU+nAPe0N5dDvekfGezt/EIiRe1TA==", "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" @@ -13158,9 +13242,9 @@ } }, "node_modules/prettier-linter-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.1.tgz", + "integrity": "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==", "license": "MIT", "dependencies": { "fast-diff": "^1.1.2" @@ -13605,9 +13689,9 @@ "license": "MIT" }, "node_modules/rollup": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz", - "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.3.tgz", + "integrity": "sha512-y9yUpfQvetAjiDLtNMf1hL9NXchIJgWt6zIKeoB+tCd3npX08Eqfzg60V9DhIGVMtQ0AlMkFw5xa+AQ37zxnAA==", "license": "MIT", "dependencies": { "@types/estree": "1.0.8" @@ -13620,28 +13704,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.53.3", - "@rollup/rollup-android-arm64": "4.53.3", - "@rollup/rollup-darwin-arm64": "4.53.3", - "@rollup/rollup-darwin-x64": "4.53.3", - "@rollup/rollup-freebsd-arm64": "4.53.3", - "@rollup/rollup-freebsd-x64": "4.53.3", - "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", - "@rollup/rollup-linux-arm-musleabihf": "4.53.3", - "@rollup/rollup-linux-arm64-gnu": "4.53.3", - "@rollup/rollup-linux-arm64-musl": "4.53.3", - "@rollup/rollup-linux-loong64-gnu": "4.53.3", - "@rollup/rollup-linux-ppc64-gnu": "4.53.3", - "@rollup/rollup-linux-riscv64-gnu": "4.53.3", - "@rollup/rollup-linux-riscv64-musl": "4.53.3", - "@rollup/rollup-linux-s390x-gnu": "4.53.3", - "@rollup/rollup-linux-x64-gnu": "4.53.3", - "@rollup/rollup-linux-x64-musl": "4.53.3", - "@rollup/rollup-openharmony-arm64": "4.53.3", - "@rollup/rollup-win32-arm64-msvc": "4.53.3", - "@rollup/rollup-win32-ia32-msvc": "4.53.3", - "@rollup/rollup-win32-x64-gnu": "4.53.3", - "@rollup/rollup-win32-x64-msvc": "4.53.3", + "@rollup/rollup-android-arm-eabi": "4.55.3", + "@rollup/rollup-android-arm64": "4.55.3", + "@rollup/rollup-darwin-arm64": "4.55.3", + "@rollup/rollup-darwin-x64": "4.55.3", + "@rollup/rollup-freebsd-arm64": "4.55.3", + "@rollup/rollup-freebsd-x64": "4.55.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.55.3", + "@rollup/rollup-linux-arm-musleabihf": "4.55.3", + "@rollup/rollup-linux-arm64-gnu": "4.55.3", + "@rollup/rollup-linux-arm64-musl": "4.55.3", + "@rollup/rollup-linux-loong64-gnu": "4.55.3", + "@rollup/rollup-linux-loong64-musl": "4.55.3", + "@rollup/rollup-linux-ppc64-gnu": "4.55.3", + "@rollup/rollup-linux-ppc64-musl": "4.55.3", + "@rollup/rollup-linux-riscv64-gnu": "4.55.3", + "@rollup/rollup-linux-riscv64-musl": "4.55.3", + "@rollup/rollup-linux-s390x-gnu": "4.55.3", + "@rollup/rollup-linux-x64-gnu": "4.55.3", + "@rollup/rollup-linux-x64-musl": "4.55.3", + "@rollup/rollup-openbsd-x64": "4.55.3", + "@rollup/rollup-openharmony-arm64": "4.55.3", + "@rollup/rollup-win32-arm64-msvc": "4.55.3", + "@rollup/rollup-win32-ia32-msvc": "4.55.3", + "@rollup/rollup-win32-x64-gnu": "4.55.3", + "@rollup/rollup-win32-x64-msvc": "4.55.3", "fsevents": "~2.3.2" } }, @@ -13759,9 +13846,9 @@ } }, "node_modules/sass": { - "version": "1.96.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.96.0.tgz", - "integrity": "sha512-8u4xqqUeugGNCYwr9ARNtQKTOj4KmYiJAVKXf2CTIivTCR51j96htbMKWDru8H5SaQWpyVgTfOF8Ylyf5pun1Q==", + "version": "1.97.2", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.97.2.tgz", + "integrity": "sha512-y5LWb0IlbO4e97Zr7c3mlpabcbBtS+ieiZ9iwDooShpFKWXf62zz5pEPdwrLYm+Bxn1fnbwFGzHuCLSA9tBmrw==", "license": "MIT", "dependencies": { "chokidar": "^4.0.0", @@ -13847,10 +13934,13 @@ } }, "node_modules/sax": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.3.tgz", - "integrity": "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==", - "license": "BlueOak-1.0.0" + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz", + "integrity": "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } }, "node_modules/saxes": { "version": "6.0.0", @@ -14602,9 +14692,9 @@ "license": "MIT" }, "node_modules/synckit": { - "version": "0.11.11", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", - "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==", + "version": "0.11.12", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz", + "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==", "license": "MIT", "dependencies": { "@pkgr/core": "^0.2.9" @@ -14630,9 +14720,9 @@ } }, "node_modules/tar": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.2.tgz", - "integrity": "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==", + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.6.tgz", + "integrity": "sha512-xqUeu2JAIJpXyvskvU3uvQW8PAmHrtXp2KDuMJwQqW8Sqq0CaZBAQ+dKS3RBXVhU4wC5NjAdKrmh84241gO9cA==", "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/fs-minipass": "^4.0.0", @@ -14682,9 +14772,9 @@ } }, "node_modules/terser": { - "version": "5.44.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.1.tgz", - "integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==", + "version": "5.46.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz", + "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==", "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -15249,9 +15339,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.2.tgz", - "integrity": "sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "funding": [ { "type": "opencollective", @@ -15352,12 +15442,12 @@ "license": "MIT" }, "node_modules/vite": { - "version": "7.2.7", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.7.tgz", - "integrity": "sha512-ITcnkFeR3+fI8P1wMgItjGrR10170d8auB4EpMLPqmx6uxElH3a/hHGQabSHKdqd4FXWO1nFIp9rRn7JQ34ACQ==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "license": "MIT", "dependencies": { - "esbuild": "^0.25.0", + "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", @@ -15705,9 +15795,9 @@ } }, "node_modules/vscode-json-languageservice": { - "version": "5.6.4", - "resolved": "https://registry.npmjs.org/vscode-json-languageservice/-/vscode-json-languageservice-5.6.4.tgz", - "integrity": "sha512-i0MhkFmnQAbYr+PiE6Th067qa3rwvvAErCEUo0ql+ghFXHvxbwG3kLbwMaIUrrbCLUDEeULiLgROJjtuyYoIsA==", + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/vscode-json-languageservice/-/vscode-json-languageservice-5.7.1.tgz", + "integrity": "sha512-sMK2F8p7St0lJCr/4IfbQRoEUDUZRR7Ud0IiSl8I/JtN+m9Gv+FJlNkSAYns2R7Ebm/PKxqUuWYOfBej/rAdBQ==", "license": "MIT", "dependencies": { "@vscode/l10n": "^0.0.18", @@ -15779,16 +15869,16 @@ "license": "MIT" }, "node_modules/vue": { - "version": "3.5.25", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.25.tgz", - "integrity": "sha512-YLVdgv2K13WJ6n+kD5owehKtEXwdwXuj2TTyJMsO7pSeKw2bfRNZGjhB7YzrpbMYj5b5QsUebHpOqR3R3ziy/g==", + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.27.tgz", + "integrity": "sha512-aJ/UtoEyFySPBGarREmN4z6qNKpbEguYHMmXSiOGk69czc+zhs0NF6tEFrY8TZKAl8N/LYAkd4JHVd5E/AsSmw==", "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.25", - "@vue/compiler-sfc": "3.5.25", - "@vue/runtime-dom": "3.5.25", - "@vue/server-renderer": "3.5.25", - "@vue/shared": "3.5.25" + "@vue/compiler-dom": "3.5.27", + "@vue/compiler-sfc": "3.5.27", + "@vue/runtime-dom": "3.5.27", + "@vue/server-renderer": "3.5.27", + "@vue/shared": "3.5.27" }, "peerDependencies": { "typescript": "*" @@ -15808,6 +15898,25 @@ "sanitize-html": "^2.0.0" } }, + "node_modules/vue-component-meta": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/vue-component-meta/-/vue-component-meta-3.2.2.tgz", + "integrity": "sha512-i1sAzQwHBXKvIFxxEoFL8+YzaJfIwyAypFOcElwXga2+J+ZxrhySiPRbnZuT9mHOEj40rkEm8Sw/93jumk7haA==", + "license": "MIT", + "dependencies": { + "@volar/typescript": "2.4.27", + "@vue/language-core": "3.2.2", + "path-browserify": "^1.0.1" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/vue-component-type-helpers": { "version": "2.2.12", "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-2.2.12.tgz", @@ -16041,9 +16150,9 @@ } }, "node_modules/vuetify": { - "version": "3.11.3", - "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-3.11.3.tgz", - "integrity": "sha512-sZe/2f143cbqtJupkynOGOHxgn+YjRrXIj0atZlBECbOr4nZPCmSdukPSbudb0wU3fQpUjlaTGx0m4vIBPQqGQ==", + "version": "3.11.7", + "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-3.11.7.tgz", + "integrity": "sha512-3nK1mKTXQRbU4QXukV4WIbs5YZgMK19flHpFq3pU+6Fpa5YLB8RyyK1BLWAW8JmhSVcaqVUcB/EJ3oJ8g3XNCw==", "license": "MIT", "funding": { "type": "github", @@ -16089,9 +16198,9 @@ } }, "node_modules/watchpack": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", - "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", + "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", "license": "MIT", "dependencies": { "glob-to-regexp": "^0.4.1", @@ -16111,9 +16220,9 @@ } }, "node_modules/webpack": { - "version": "5.103.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.103.0.tgz", - "integrity": "sha512-HU1JOuV1OavsZ+mfigY0j8d1TgQgbZ6M+J75zDkpEAwYeXjWSqrGJtgnPblJjd/mAyTNQ7ygw0MiKOn6etz8yw==", + "version": "5.104.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.104.1.tgz", + "integrity": "sha512-Qphch25abbMNtekmEGJmeRUhLDbe+QfiWTiqpKYkpCOWY64v9eyl+KRRLmqOFA2AvKPpc9DC6+u2n76tQLBoaA==", "license": "MIT", "dependencies": { "@types/eslint-scope": "^3.7.7", @@ -16124,10 +16233,10 @@ "@webassemblyjs/wasm-parser": "^1.14.1", "acorn": "^8.15.0", "acorn-import-phases": "^1.0.3", - "browserslist": "^4.26.3", + "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.3", - "es-module-lexer": "^1.2.1", + "enhanced-resolve": "^5.17.4", + "es-module-lexer": "^2.0.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", @@ -16138,7 +16247,7 @@ "neo-async": "^2.6.2", "schema-utils": "^4.3.3", "tapable": "^2.3.0", - "terser-webpack-plugin": "^5.3.11", + "terser-webpack-plugin": "^5.3.16", "watchpack": "^2.4.4", "webpack-sources": "^3.3.3" }, @@ -16364,12 +16473,12 @@ } }, "node_modules/webpack-plugin-vuetify": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/webpack-plugin-vuetify/-/webpack-plugin-vuetify-3.1.2.tgz", - "integrity": "sha512-8hVvrDk9bieuNrCHj+IVei658C5P9CDqmkKgxqp0PxVeZBGYHb6tR1BvF5RNY9AqONI6OoQFUf7UdE9o4U4lgQ==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/webpack-plugin-vuetify/-/webpack-plugin-vuetify-3.1.3.tgz", + "integrity": "sha512-0o3H+UEgeZh47vjgLeHbbFl+oZUpnRadRUyk6xCMXSZQR1Jh0ZnfzYC79+fO83OWlMpv2AqHbJkEXwDKnCafYA==", "license": "MIT", "dependencies": { - "@vuetify/loader-shared": "^2.1.1", + "@vuetify/loader-shared": "^2.1.2", "decache": "^4.6.0", "file-loader": "^6.2.0", "find-cache-dir": "^5.0.0", @@ -16384,7 +16493,7 @@ "peerDependencies": { "@vue/compiler-sfc": "^3.2.6", "vue": "^3.2.6", - "vuetify": "^3.0.0", + "vuetify": ">=3", "webpack": "^5.0.0" } }, @@ -16510,6 +16619,12 @@ "node": ">=10.13.0" } }, + "node_modules/webpack/node_modules/es-module-lexer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", + "license": "MIT" + }, "node_modules/webpack/node_modules/eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -16536,6 +16651,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", "license": "MIT", "dependencies": { "iconv-lite": "0.6.3" @@ -16646,9 +16762,9 @@ } }, "node_modules/which-typed-array": { - "version": "1.1.19", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", - "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", + "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", @@ -17303,9 +17419,9 @@ "license": "ISC" }, "node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", "license": "MIT", "engines": { "node": ">=10.0.0" diff --git a/frontend/package.json b/frontend/package.json index 2b35e4ee5..5de465885 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -33,24 +33,24 @@ ">0.25% and last 2 years" ], "dependencies": { - "@babel/cli": "^7.28.3", - "@babel/core": "^7.28.5", + "@babel/cli": "^7.28.6", + "@babel/core": "^7.28.6", "@babel/plugin-transform-runtime": "^7.28.5", - "@babel/preset-env": "^7.28.5", - "@babel/register": "^7.28.3", - "@babel/runtime": "^7.28.4", + "@babel/preset-env": "^7.28.6", + "@babel/register": "^7.28.6", + "@babel/runtime": "^7.28.6", "@eslint/eslintrc": "^3.3.3", "@eslint/js": "^9.33.0", "@mdi/font": "^7.4.47", "@testing-library/jest-dom": "^6.9.1", - "@testing-library/react": "^16.3.0", + "@testing-library/react": "^16.3.2", "@vitejs/plugin-react": "^5.1.2", "@vitejs/plugin-vue": "^6.0.3", "@vitest/browser": "^3.2.4", "@vitest/coverage-v8": "^3.2.4", "@vitest/ui": "^3.2.4", "@vue/compiler-sfc": "^3.5.18", - "@vue/language-server": "^3.1.8", + "@vue/language-server": "^3.2.2", "@vue/test-utils": "^2.4.6", "@vvo/tzdb": "^6.198.0", "axios": "^1.13.2", @@ -65,14 +65,14 @@ "css-loader": "^7.1.2", "cssnano": "^7.1.2", "escape-string-regexp": "^4.0.0", - "eslint": "^9.39.1", + "eslint": "^9.39.2", "eslint-config-prettier": "^10.1.8", "eslint-formatter-pretty": "^6.0.1", "eslint-plugin-html": "^8.1.3", "eslint-plugin-import": "^2.32.0", "eslint-plugin-node": "^11.1.0", - "eslint-plugin-prettier": "^5.5.4", - "eslint-plugin-vue": "^10.6.2", + "eslint-plugin-prettier": "^5.5.5", + "eslint-plugin-vue": "^10.7.0", "eslint-plugin-vuetify": "^2.5.3", "eslint-webpack-plugin": "^5.0.2", "eventsource-polyfill": "^0.9.6", @@ -84,9 +84,9 @@ "i": "^0.3.7", "jsdom": "^26.1.0", "luxon": "^3.7.2", - "maplibre-gl": "^5.14.0", + "maplibre-gl": "^5.16.0", "memoize-one": "^6.0.0", - "mini-css-extract-plugin": "^2.9.4", + "mini-css-extract-plugin": "^2.10.0", "minimist": "^1.2.8", "node-storage-shim": "^2.0.1", "passive-events-support": "^1.1.0", @@ -95,20 +95,20 @@ "postcss": "^8.5.6", "postcss-import": "^16.1.1", "postcss-loader": "^8.2.0", - "postcss-preset-env": "^10.5.0", + "postcss-preset-env": "^10.6.1", "postcss-reporter": "^7.1.0", "postcss-url": "^10.1.3", - "prettier": "^3.7.4", + "prettier": "^3.8.0", "pubsub-js": "^1.9.5", "regenerator-runtime": "^0.14.1", "resolve-url-loader": "^5.0.0", "sanitize-html": "^2.17.0", - "sass": "^1.96.0", + "sass": "^1.97.2", "sass-loader": "^16.0.6", "sockette": "^2.0.6", "style-loader": "^4.0.0", "svg-url-loader": "^8.0.0", - "tar": "^7.5.2", + "tar": "^7.5.6", "url-loader": "^4.1.1", "util": "^0.12.5", "vite-tsconfig-paths": "^5.1.4", @@ -122,15 +122,15 @@ "vue-sanitize-directive": "^0.2.1", "vue-style-loader": "^4.1.3", "vue3-gettext": "^2.4.0", - "vuetify": "^3.11.3", - "webpack": "^5.103.0", + "vuetify": "^3.11.7", + "webpack": "^5.104.1", "webpack-bundle-analyzer": "^4.10.2", "webpack-cli": "^6.0.1", "webpack-hot-middleware": "^2.26.1", "webpack-manifest-plugin": "^5.0.1", "webpack-md5-hash": "^0.0.6", "webpack-merge": "^6.0.1", - "webpack-plugin-vuetify": "^3.1.2", + "webpack-plugin-vuetify": "^3.1.3", "workbox-webpack-plugin": "^7.4.0" }, "engines": { diff --git a/frontend/src/locales.js b/frontend/src/locales.js index b271c8f55..d55de3a1c 100644 --- a/frontend/src/locales.js +++ b/frontend/src/locales.js @@ -196,6 +196,10 @@ export let Options = [ value: "fa", rtl: true, }, + { + text: "Latviešu", // Latvian + value: "lv", + }, ]; // Returns the Vuetify UI messages translated with Gettext. diff --git a/frontend/src/locales/json/lv.json b/frontend/src/locales/json/lv.json new file mode 100644 index 000000000..ebec477a2 --- /dev/null +++ b/frontend/src/locales/json/lv.json @@ -0,0 +1 @@ +{"lv":{"{0} appended action":"{0} pievienotā darbība","{0} files":"{0} faili","{0} files ({1} in total)":"{0} faili (kopā {1})","{0} more":"{0} vairāk","{0} prepended action":"{0} pievienota darbība","{0} selected":"Atlasīts {0}","{0}-{1} of {2}":"{0}-{1} no {2}","%{n} albums found":"Atrasti %{n} albumi","%{n} files uploaded":"Augšupielādēti %{n} faili","%{n} folders found":"Atrastas %{n} mapes","%{n} GB of %{q} GB used":"Izmantots %{n} GB no %{q} GB","%{n} labels found":"Atrastas %{n} birkas","%{n} people found":"Atrasti %{n} cilvēki","%{n} pictures found":"Atrasti %{n} attēli","%{s} is too long":"%{s} ir pārāk garš","%{s} is too short":"%{s} ir pārāk īss","1 hour":"1 stunda","12 hours":"12 stundas","2-Factor Authentication":"Divfaktoru autentifikācija","2FA":"2FA","4 hours":"4 stundas","A click will copy it to your clipboard.":"Klikšķis nokopēs to starpliktuvē.","A new version of %{s} is available:":"Ir pieejama jauna %{s} versija:","About":"Par programmu","Access Token":"Piekļuves žetons","Account":"Konts","Account created":"Konts izveidots","Account deleted":"Konts ir dzēsts","Account restored":"Konts ir atjaunots","Accounts":"Konti","Accuracy":"Precizitāte","Action":"Darbība","Actions":"Darbības","Activate":"Aktivizēt","Activate to remove sorting.":"Aktivizējiet, lai noņemtu kārtošanu.","Activate to sort ascending.":"Aktivizējiet, lai kārtotu augošā secībā.","Activate to sort descending.":"Aktivizējiet, lai kārtotu dilstošā secībā.","Activation Code":"Aktivizācijas kods","Activity":"Aktivitāte","Add":"Pievienot","Add %{s}?":"Pievienot %{s}?","Add Account":"Pievienot kontu","Add Album":"Pievienot albumu","Add Link":"Pievienot saiti","Add person?":"Pievienot personu?","Add pictures from search results by selecting them.":"Pievienojiet attēlus no meklēšanas rezultātiem, tos atlasot.","Add to album":"Pievienot albumam","Add to all":"Pievienot visiem","Add to all selected photos":"Pievienot visiem atlasītajiem fotoattēliem","Added":"Pievienots","Adjust Location":"Pielāgot atrašanās vietu","Admin":"Administrators","Advanced":"Paplašināts","After 1 day":"Pēc 1 dienas","After 3 days":"Pēc 3 dienām","After 7 days":"Pēc 7 dienām","After entering your password for confirmation, you can set up two-factor authentication with a compatible authenticator app or device:":"Pēc paroles ievadīšanas apstiprināšanai varat iestatīt divfaktoru autentifikāciju, izmantojot saderīgu autentifikācijas lietotni vai ierīci:","After one month":"Pēc viena mēneša","After one year":"Pēc viena gada","After selecting pictures from search results, you can add them to an album using the context menu.":"Pēc attēlu atlasīšanas meklēšanas rezultātos tos var pievienot albumam, izmantojot konteksta izvēlni.","After two months":"Pēc diviem mēnešiem","After two weeks":"Pēc divām nedēļām","Album":"Albums","Album Backups":"Albuma dublējumkopijas","Album created":"Albums izveidots","Album deleted":"Albums ir izdzēsts","Albums":"Albumi","Albums deleted":"Albumi ir dzēsti","All":"Visi","All %{n} albums loaded":"Visi %{n} albumi ir ielādēti","All %{n} labels loaded":"Visas %{n} etiķetes ir ielādētas","All %{n} people loaded":"Visi %{n} cilvēki ielādēti","All Cameras":"Visas kameras","All Categories":"Visas kategorijas","All Colors":"Visas krāsas","All Countries":"Visas valstis","All fields are required":"Visi lauki ir obligāti jāaizpilda","All files from import folder":"Visi faili no importēšanas mapes","All Lenses":"Visi objektīvi","All Months":"Visi mēneši","All originals":"Visi oriģināli","All Years":"Visi gadi","Allow configuration and use of connected apps and services for remote uploads and sync.":"Atļaut savienoto lietotņu un pakalpojumu konfigurēšanu un izmantošanu attālinātai augšupielādei un sinhronizācijai.","Allow editing of metadata such as title, description, date, and location.":"Atļaut rediģēt metadatus, piemēram, nosaukumu, aprakstu, datumu un atrašanās vietu.","Allow editing the metadata, labels, and albums of multiple pictures at once.":"Ļauj vienlaikus rediģēt vairāku attēlu metadatus, etiķetes un albumus.","Allow files to be copied or moved from the Import to the Originals folder.":"Atļaut failu kopēt vai pārvietot no importēšanas mapes uz oriģinālu mapi.","Allow files to be permanently deleted to free up storage space.":"Atļaujiet failu neatgriezenisku dzēšanu, lai atbrīvotu vietu krātuvē.","Allow users to archive photos and videos so they are hidden without being deleted.":"Ļaut lietotājiem arhivēt fotoattēlus un videoklipus, lai tie tiktu paslēpti, bet netiktu dzēsti.","Allow users to create and share links, and enable sharing with connected services.":"Atļaut lietotājiem izveidot un kopīgot saites un iespējot kopīgošanu ar pievienotajiem pakalpojumiem.","Allow users to upload new photos and videos through the web interface.":"Ļaut lietotājiem augšupielādēt jaunus fotoattēlus un videoklipus, izmantojot tīmekļa saskarni.","Alternatively, you can upload files directly to WebDAV servers like Nextcloud.":"Varat arī augšupielādēt failus tieši WebDAV serveros, piemēram, Nextcloud.","Altitude":"Augstums","Altitude (m)":"Augstums (m)","AM":"AM","Animated":"Animēts","Animation":"Animācija","Animations":"Animācijas","Any private photos and videos remain private and won't be shared.":"Visi privātie fotoattēli un videoklipi paliek privāti un netiks kopīgoti.","API Key":"API atslēga","Application":"Programma","Applications":"Programmas","Approve":"Apstiprināt","Approve and save changes":"Apstiprināt un saglabāt izmaiņas","Apps and Devices":"Lietotnes un ierīces","Archived":"Arhivēts","Are you sure you want to archive the selection?":"Vai tiešām vēlaties arhivēt atlasi?","Are you sure you want to delete these albums?":"Vai tiešām vēlaties dzēst šos albumus?","Are you sure you want to delete these labels?":"Vai tiešām vēlaties dzēst šīs birkas?","Are you sure you want to delete this account?":"Vai tiešām vēlaties dzēst šo kontu?","Are you sure you want to delete this album?":"Vai tiešām vēlaties dzēst šo albumu?","Are you sure you want to permanently delete these pictures?":"Vai tiešām vēlaties neatgriezeniski dzēst šos attēlus?","Are you sure you want to permanently delete this file?":"Vai tiešām vēlaties neatgriezeniski dzēst šo failu?","Are you sure?":"Vai esi pārliecināts?","Artist":"Mākslinieks","Aspect Ratio":"Malu attiecība","Attributes":"Atribūti","Audio":"Audio","Audit Log":"Audita žurnāls","Authentication":"Autentifikācija","Auto":"Automātiski","Back":"Atpakaļ","Back to top":"Atpakaļ uz augšu","Backup":"Rezerves kopija","Badge":"Žetons","Base Path":"Saknes ceļš","Batch":"Partija","Batch Edit":"Partijas rediģēšana","Become a member today, support our mission and enjoy our member benefits!":"Kļūsti par biedru jau šodien, atbalsti mūsu misiju un izbaudi mūsu biedru priekšrocības!","Being 100% self-funded and independent, we can promise you that we will never sell your data and that we will always be transparent about our software and services.":"Tā kā mēs esam 100% pašfinansēti un neatkarīgi, mēs varam jums apsolīt, ka nekad nepārdosim jūsu datus un vienmēr būsim caurspīdīgi attiecībā uz mūsu programmatūru un pakalpojumiem.","Bio":"Biogrāfija","Birth Date":"Dzimšanas datums","Black":"Melns","Blackman: Lanczos Modification, Less Ringing Artifacts":"Blackman: Lanczos modifikācija, mazāk nevēlami efekti","Blue":"Zils","Brown":"Brūns","Browse":"Pārlūkot","Browse Files":"Pārlūkot failus","Browse Pictures":"Pārlūkot attēlus","Bug Report":"Kļūdas ziņojums","Busy, please wait…":"Aizņemts, lūdzu, uzgaidiet…","By using the software and services we provide, you agree to our terms of service, privacy policy, and code of conduct.":"Izmantojot mūsu piedāvāto programmatūru un pakalpojumus, jūs piekrītat mūsu pakalpojumu sniegšanas noteikumiem, privātuma politikai un rīcības kodeksam.","Calendar":"Kalendārs","Camera":"Kamera","Camera Serial":"Kameras sērijas numurs","Can't access your authenticator app or device?":"Nevarat piekļūt autentifikatora lietotnei vai ierīcei?","Can't load more, limit reached":"Nevar ielādēt vairāk, sasniegts ierobežojums","Can't select more items":"Nevar atlasīt vairāk ierakstu","Cancel":"Atcelt","Cannot copy to clipboard":"Nevar nokopēt starpliktuvē","Caption":"Paraksts","Cards":"Kartītes","Carousel slide {0} of {1}":"Karuseļa slaids {0} no {1}","Category":"Kategorija","Change Avatar":"Mainīt avatāru","Change Password":"Mainīt paroli","Change private flag":"Mainīt privāto atzīmi","Changes could not be saved":"Izmaiņas nevarēja saglabāt","Changes successfully saved":"Izmaiņas veiksmīgi saglabātas","Changes to the advanced settings require a restart to take effect.":"Lai izmaiņas papildu iestatījumos stātos spēkā, ir nepieciešama restartēšana.","Checked":"Pārbaudīts","Chroma":"Hroma","Cleaning index and cache":"Notiek indeksa un kešatmiņas tīrīšana","Cleanup":"Tīrīšana","Clear {0}":"Notīrīt {0}","Client":"Klients","Client Credentials":"Klienta piekļuves dati","Close":"Aizvērt","Cluster":"Klasteris","Codec":"Kodeks","Color":"Krāsa","Color Profile":"Krāsu profils","Colors":"Krāsas","Compare Editions":"Salīdzināt izdevumus","Complete Rescan":"Veikt pilnu pārlūkošanu","Confidence":"Pārliecība","Confirm":"Apstiprināt","Connect":"Savienot","Connect via WebDAV":"Savienot caur WebDAV","Connected":"Savienots","Contact Details":"Kontaktinformācija","Contact Us":"Sazinieties ar mums","Contains %{n} pictures.":"Satur %{n} attēlus.","Contains one picture.":"Satur vienu attēlu.","Content":{"":"Saturs","Edit":"Saturs"},"Continue":"Turpināt","Contributor":"Dalībnieks","Converting":"Konvertēšana","Copied to clipboard":"Kopēts starpliktuvē","Copy":"Kopēt","Copyright":"Autortiesības","Could not update person cover":"Neizdevās atjaunināt personas profila attēlu","Couldn't find anything.":"Neko nevarēja atrast.","Country":"Valsts","Create Account":"Izveidot kontu","Create album":"Izveidot albumu","Create regular backups based on the configured schedule.":"Veidot regulāras rezerves kopijas saskaņā ar plānoto grafiku.","Create YAML files to back up album metadata.":"Izveidojiet YAML failus, lai dublētu albuma metadatus.","Create YAML sidecar files to back up picture metadata.":"Izveidojiet YAML blakusfailus, lai dublētu attēlu metadatus.","Created":"Izveidots","Creating thumbnails for":"Sīktēlu izveide priekš","Cubic: Moderate Quality, Good Performance":"Cubic: vidēja kvalitāte, laba veiktspēja","Current Password":"Esošā parole","Custom":"Pielāgots","Cyan":"Gaiši zils","Daily":"Dienas","Database":"Datubāze","Database Backups":"Datu bāzes rezerves kopijas","Databases":"Datu bāzes","Date & Time":"Datums un laiks","Day":"Diena","Deactivate":"Deaktivizēt","Debug Logs":"Atkļūdošanas žurnāli","Default":"Noklusējuma","Default Folder":"Noklusējuma mape","Delete":"Dzēst","Delete Album":"Dzēst albumu","Delete All":"Dzēst visu","Delete all?":"Dzēst visu?","Delete orphaned index entries, sidecar files and thumbnails.":"Dzēst nevajadzīgos indeksa ierakstus, blakusfailus un sīktēlus.","deleted":"izdzēsts","Description":"Apraksts","Detailed instructions can be found in our User Guide.":"Detalizētus norādījumus var atrast mūsu lietotāja rokasgrāmatā.","Details":"Sīkāka informācija","Dimensions":"Izmēri","Disable all face detection and recognition features.":"Izslēgt visas sejas atpazīšanas un noteikšanas iespējas.","Disable Backups":"Izslēgt rezerves kopijas","Disable Darktable":"Izslēgt Darktable","Disable ExifTool":"Izslēgt ExifTool","Disable Faces":"Izslēgt sejas","Disable features that require write permission for the originals folder.":"Izslēgt funkcijas, kurām nepieciešama rakstīšanas atļauja oriģinālu mapē.","Disable FFmpeg":"Izslēgt FFmpeg","Disable ImageMagick":"Izslēgt ImageMagick","Disable interactive world maps and reverse geocoding.":"Izslēgt interaktīvo pasaules karti un adreses noteikšanu pēc koordinātēm.","Disable Places":"Izslēgt vietas","Disable RawTherapee":"Izslēgt RawTherapee","Disable Vectors":"Izslēgt vektorus","Disable WebDAV":"Izslēgt WebDAV","Disabled":"Izslēgt","Disables vector graphics support.":"Izslēdz vektorgrafikas atbalstu.","Disables video transcoding and thumbnail extraction.":"Izslēdz video pārkodēšanu un sīktēlu izgūšanu.","Discard":"Atcelt","Discard changes and close":"Atcelt izmaiņas un aizvērt","Discover":"Atklāt","Dismiss":"Noraidīt","Display Name":"Parādāmais vārds","Display picture captions in search results.":"Rādīt attēlu parakstus meklēšanas rezultātos.","Display picture titles in search results.":"Rādīt attēlu nosaukumus meklēšanas rezultātos.","Document":"Dokuments","Documents":"Dokumenti","Don't use Darktable to convert RAW images.":"Neizmantojiet Darktable, lai konvertētu RAW attēlus.","Don't use ImageMagick to convert images.":"Neizmantojiet ImageMagick attēlu konvertēšanai.","Don't use RawTherapee to convert RAW images.":"Neizmantojiet RawTherapee, lai konvertētu RAW attēlus.","Done":"Pabeigts","Done.":"Pabeigts.","Download":"Lejupielādēt","Download only original media files, without any automatically generated files.":"Lejupielādējiet tikai oriģinālos multimediju failus, neizmantojot automātiski ģenerētus failus.","Download remote files":"Lejupielādēt attālinātos failus","Downloading…":"Notiek lejupielāde…","Downscaling Filter":"Samazināšanas filtrs","Drag and drop files here":"Velciet un nometiet failus šeit","Driver":"Draiveris","Due to the high volume of emails we receive, our team may be unable to get back to you immediately.":"Lielā e-pasta ziņojumu skaita dēļ, ko saņemam, mūsu komanda, iespējams, nevarēs ar jums nekavējoties atbildēt.","Duplicates will be skipped and only appear once.":"Dublikāti tiks izlaisti un parādīsies tikai vienu reizi.","Duration":"Ilgums","Dynamic Previews":"Dinamiskie priekšskatījumi","Dynamic Size Limit: %{n}px":"Dinamiskā izmēra ierobežojums: %{n}px","E-Mail":"E-pasts","Edit":"Rediģēt","Edit %{s}":"Rediģēt %{s}","Edit Account":"Rediģēt kontu","Edit Photo":"Rediģēt fotoattēlu","Edit Photos (%{n})":"Rediģēt fotoattēlus (%{n})","Edited":"Rediģēts","Email":"E-pasts","Enable debug mode to display additional logs and help with troubleshooting.":"Ieslēgt atkļūdošanas režīmu, lai parādītu papildu žurnālfailus un palīdzētu problēmu novēršanā.","Enable downloading of original and sidecar files from the web interface.":"Ieslēgt lietotāja saskarnē oriģinālo un blakusfailu lejupielādi.","Enable face recognition and the People view to easily find people you know.":"Ieslēgt sejas atpazīšanu un personas skatu, lai viegli atrastu cilvēkus, kurus pazīstat.","Enable new features that may be incomplete or unstable.":"Ieslēgt funkcionalitāti, kas varētu būt vēl nepilnīga vai nestabila.","Enable the file browser to navigate the Originals folder structure.":"Ieslēgt failu pārlūku, lai pārvietotos pa oriģinālu mapi.","Enables RAW converter presets. May reduce performance.":"Ieslēdz RAW pārveidotāja sākotnējos iestatījumus. Var samazināt veiktspēju.","Enabling two-factor authentication means that you will need a randomly generated verification code to log in, so even if someone gains access to your password, they will not be able to access your account.":"Divfaktoru autentifikācijas ieslēgšana nozīmē to, ka, lai pieteiktos, būs nepieciešams nejauši ģenerēts verifikācijas kods, tāpēc pat ja kāds iegūs piekļuvi jūsu parolei, viņš nevarēs piekļūt jūsu kontam.","Engine":"Dzinējs","Enter date":"Ievadiet datumu","Enter dates":"Ievadiet datumus","Enter item name...":"Ievadiet nosaukumu...","Enter the code generated by your authenticator app:":"Ievadiet autentifikācijas lietotnes ģenerēto kodu:","Enter verification code":"Ievadiet verifikācijas kodu","Enter your password to confirm the action and continue:":"Ievadiet paroli, lai apstiprinātu darbību un turpinātu:","Error":"Kļūda","Errors":"Kļūdas","Estimate":"Noteikt","Estimate Locations":"Aptuvenās atrašanās vietas","Estimate the approximate location of pictures without GPS coordinates.":"Noteikt aptuveno attēlu atrašanās vietu bez GPS koordinātām.","Every two days":"Ik pēc divām dienām","Exclude hidden":"Izlaist slēptos","ExifTool is required for full support of XMP metadata, videos and Live Photos.":"Pilnīgam XMP metadatu, videoklipu un Live Photos atbalstam ir nepieciešams ExifTool.","Experimental Features":"Eksperimentālās funkcijas","Expires":"Derīguma termiņš beidzas","Exposure":"Ekspozīcija","Extract still images and generate thumbnails while indexing.":"Izgūt statiskos attēlus un ģenerēt sīktēlus indeksēšanas laikā.","F Number":"F skaitlis","Face":"Seja","Faces":"Sejas","Failed removing link":"Neizdevās noņemt saiti","Failed to connect account.":"Neizdevās savienot kontu.","Failed to save changes":"Neizdevās saglabāt izmaiņas","Failed updating link":"Neizdevās atjaunināt saiti","Family Name":"Uzvārds","Fast":"Ātri","Favorite":"Favorīts","Favorites":"Favorīti","Feature Request":"Funkcionalitātes pieprasījums","Feedback":"Atsauksmes","Female":"Sieviete","File":"Fails","File Browser":"Failu pārlūks","File Conversion":"Failu konvertēšana","File Name":"Faila nosaukums","File Size":"Faila izmērs","Filename":"Faila nosaukums","Files":"Faili","Files with sequential names like 'IMG_1234 (2)' and 'IMG_1234 (3)' belong to the same picture.":"Faili ar secīgiem nosaukumiem, piemēram, “IMG_1234 (2)” un “IMG_1234 (3)”, pieder vienam attēlam.","First page":"Pirmā lapa","Focal Length":"Fokusa attālums","Folder":"Mape","Folder contains %{n} files":"Mapē ir %{n} faili","Folder is empty":"Mape ir tukša","Folders":"Mapes","Forgot password?":"Aizmirsu paroli?","FPS":"FPS","Frames":"Rāmji","Full Access":"Pilna piekļuve","Fullscreen":"Pilnekrāna režīms","General":"Vispārīgi","Generate":"Ģenerēt","Generate Previews":"Ģenerēt priekšskatījumus","Generated":"Ģenerēts","Get Started":"Sākt darbu","Given Name":"Vārds","Global Options":"Globālās opcijas","Go to page {0}":"Dodieties uz {0} lapu","Gold":"Zelts","Green":"Zaļš","Grey":"Pelēks","Guest":"Viesis","Hash":"Hašs","Help":"Palīdzība","Help & Support":"Palīdzība un atbalsts","Hidden":"Slēpts","Hidden Files":"Slēptie faili","Hide":"Slēpt","Hide private content from global views while keeping it accessible in the Private section.":"Slēpt privātu saturu no globāliem skatiem, vienlaikus saglabājot to pieejamu sadaļā Privāts.","High":"Augsts","High Dynamic Range (HDR)":"Augsts dinamiskais diapazons (HDR)","How can we help?":"Kā mēs varam palīdzēt?","Hybrid":"Hibrīds","If you lose access to your authenticator app or device, you can use your recovery code to regain access to your account.":"Ja zaudējat piekļuvi autentifikatora lietotnei vai ierīcei, varat izmantot atkopšanas kodu, lai atgūtu piekļuvi savam kontam.","Image":"Attēls","Image Quality":"Attēla kvalitāte","Import":"Importēt","Imported files will be sorted by date and given a unique name to avoid duplicates.":"Importētie faili tiks sakārtoti pēc datuma un tiem tiks piešķirts unikāls nosaukums, lai izvairītos no dublikātiem.","Importing %{s}…":"Notiek %{s} importēšana…","Importing files to originals…":"Failu importēšana uz oriģināliem…","in":"iekšā","In case pictures you expect are missing, please rescan your library and wait until indexing has been completed.":"Ja trūkst gaidīto attēlu, lūdzu, atkārtoti skenējiet savu bibliotēku un pagaidiet, līdz indeksēšana ir pabeigta.","Include RAW image files when downloading stacks and archives.":"Lejupielādējot kaudzes un arhīvus, iekļaujiet RAW attēlu failus.","Include sidecar files when downloading stacks and archives.":"Lejupielādējot stekus un arhīvus, iekļaujiet blakusfailus.","Increase storage size or delete files to continue.":"Lai turpinātu, palieliniet krātuves lielumu vai izdzēsiet failus.","Index":"Indekss","Indexing":"Indeksēšana","Indexing media and sidecar files…":"Notiek multivides un blakusfailu indeksēšana…","Information":"Informācija","Instance":"Instance","Instance ID":"Instances ID","Instances":"Instances","Insufficient storage.":"Brīvas vietas vairs nav.","Interval":"Intervāls","Invalid":"Nederīgs","Invalid address":"Nederīga adrese","Invalid country":"Nederīga valsts","Invalid date":"Nederīgs datums","Invalid parameters":"Nederīgi parametri","Invalid photo selected":"Atlasīts nederīgs fotoattēls","Invalid time":"Nederīgs laiks","Invalid URL":"Nederīgs URL","IP Address":"IP adrese","It is a one-time use code that will disable 2FA for your account when you use it.":"Tas ir vienreiz lietojams kods, kas atspējos 2FA jūsu kontam, kad to izmantosiet.","Item":"Ieraksts","Items per page:":"Vienumi lapā:","JPEG Quality: %{n}":"JPEG kvalitāte: %{n}","JPEG Size Limit: %{n}px":"JPEG izmēra ierobežojums: %{n} pikseļi","JPEGs and thumbnails are automatically rendered as needed.":"JPEG faili un to sīktēli tiek automātiski radīti, kad nepieciešams.","Keyword":"Atslēgvārds","Keywords":"Atslēgvārdi","Label":"Birka","Labels":"Birkas","Labels deleted":"Birkas ir dzēstas","Lanczos: Detail Preservation, Minimal Artifacts":"Lanczos: Detaļu saglabāšana, minimāli nevēlami efekti","Language":"Valoda","Last Active":"Pēdējais aktīvais","Last Login":"Pēdējā pieteikšanās","Last page":"Pēdējā lapa","Last Sync":"Pēdējā sinhronizācija","Last Used":"Pēdējo reizi izmantots","Latitude":"Platums","LDAP/AD":"LDAP/AD","Learn more":"Uzziniet vairāk","Learn More":"Uzziniet vairāk","Legal Information":"Juridiskā informācija","Lens":"Objektīvs","Library":"Bibliotēka","License":"Licence","Like":"Patīk","Lime":"Gaiši zaļš","Limit reached, showing first %{n} files":"Sasniegts limits, tiek rādīti pirmie %{n} faili","Linear: Very Smooth, Best Performance":"Lineārs: ļoti vienmērīgs, vislabākā veiktspēja","Link":"Saite","List":"Saraksts","List View":"Saraksta skats","Live":"Tiešraide","Live Photos":"Tiešraides fotoattēli","Load more":"Ielādēt vairāk","Loading items...":"Notiek vienumu ielāde...","Loading...":"Notiek ielāde...","Local":"Vietējais","Local Time":"Vietējais laiks","Location":"Atrašanās vieta","Log messages appear here whenever PhotoPrism comes across broken files, or there are other potential issues.":"Žurnāla ziņojumi tiek parādīti šeit ikreiz, kad PhotoPrism atrod bojātus failus vai pastāv citas potenciālas problēmas.","Login":"Pieteikšanās","Logout":"Izrakstīties","Logs":"Žurnāli","Longitude":"Garums","Lost server connection":"Zaudēts savienojums ar serveri","Low":"Zems","Magenta":"Fuksīna","Main Color":"Galvenā krāsa","Male":"Vīrietis","Manage Account":"Pārvaldīt kontu","Manager":"Vadītājs","Manual":"Manuāli","Manual Upload":"Manuāla augšupielāde","Maps":"Kartes","Marker":"Marķieris","Maximum number of accounts has been reached.":"Ir sasniegts maksimālais kontu skaits.","Media":"Mediji","Medium":"Vidējs","Membership":"Dalība","Merge %{a} with %{b}?":"Apvienot %{a} ar %{b}?","Message":"Ziņojums","Message sent":"Ziņojums nosūtīts","Metadata":"Metadati","Metrics":"Metrika","Minimize":"Minimizēt","Missing":"Trūkst","Missing or invalid configuration":"Trūkstoša vai nederīga konfigurācija","mixed":"jaukts","Moments":"Mirkļi","Monochrome":"Vienkrāsains","Month":"Mēnesis","More options":"Vairāk iespēju","Mosaic":"Mozaīka","Most Relevant":"Visatbilstošākais","Move Files":"Pārvietot failus","Must have at least %{n} characters.":"Jābūt vismaz %{n} rakstzīmēm.","Mute":"Izslēgt skaņu","Name":"Vārds","Name too long":"Vārds ir pārāk garš","Never":"Nekad","New":"Jauns","New Password":"Jauna parole","Newest First":"Jaunākie vispirms","Next":"Nākamais","Next page":"Nākamā lapa","Next visual":"Nākamais vizuālais materiāls","No":"Nē","No albums assigned":"Nav piešķirts neviens albums","No albums found":"Nav atrasts neviens albums","No data available":"Dati nav pieejami","No labels assigned":"Nav piešķirtas etiķetes","No labels found":"Nav atrastas etiķetes","No matching records found":"Nav atrasti atbilstoši ieraksti","No more":"Vairs ne","No people found":"Nav atrastas personas","No pictures found":"Nav atrasts neviens attēls","No recently edited pictures":"Nav nesen rediģētu attēlu","No results":"Nav rezultātu","No servers configured.":"Nav konfigurētu serveru.","No services configured.":"Nav konfigurētu pakalpojumu.","No thanks":"Nē, paldies","No warnings or error containing this keyword. Note that search is case-sensitive.":"Nav brīdinājumu vai kļūdu, kas satur šo atslēgvārdu. Ņemiet vērā, ka meklēšana ir reģistrjutīga.","Node":"Mezgls","Nodes":"Mezgli","Non-photographic and low-quality images require a review before they appear in search results.":"Attēli, kas nav fotoattēli un ir zemas kvalitātes, ir jāpārskata, pirms tie parādās meklēšanas rezultātos.","None":"Neviens","Not allowed":"Nav atļauts","Not found":"Nav atrasts","Not sorted.":"Nav sakārtots.","Not supported":"Nav atbalstīts","Note you may manually manage your originals folder and importing is optional.":"Ņemiet vērā, ka varat manuāli pārvaldīt savu oriģinālu mapi, un importēšana nav obligāta.","Note:":"Piezīme:","Note: Only WebDAV servers, like Nextcloud or PhotoPrism, can be configured as remote service for backup and file upload.":"Piezīme. Kā attālinātus pakalpojumus dublēšanai un failu augšupielādei var konfigurēt tikai WebDAV serverus, piemēram, Nextcloud vai PhotoPrism.","Notes":"Piezīmes","Nothing to see here yet.":"Šeit vēl nav ko redzēt.","Nothing was found.":"Nekas netika atrasts.","Offline":"Bezsaistē","OIDC":"OIDC","OK":"Labi","Oldest First":"Vecākais vispirms","On Windows, enter the following resource in the connection dialog:":"Operētājsistēmā Windows savienojuma dialoglodziņā ievadiet šādu resursu:","On-demand generation of thumbnails may cause high CPU and memory usage. It is not recommended for resource-constrained servers and NAS devices.":"Sīktēlu ģenerēšana pēc pieprasījuma var izraisīt lielu centrālā procesora un atmiņas izmantošanu. Tas nav ieteicams serveriem un NAS ierīcēm ar ierobežotiem resursiem.","Once a week":"Reizi nedēļā","One album found":"Atrasts viens albums","One file found":"Atrasts viens fails","One file uploaded":"Augšupielādēts viens fails","One folder found":"Atrasta viena mape","One label found":"Atrasta viena etiķete","One person found":"Atrasta viena persona","One picture found":"Atrasta viena bilde","Only locally managed accounts can be set up for authentication with 2FA.":"Divfaktoru autentifikāciju var iestatīt tikai lokāli pārvaldītiem kontiem.","Open":"Atvērt","optional":"neobligāts","or":"vai","Orange":"Oranžs","Organization":"Organizācija","Orientation":"Orientācija","Original file names will be stored and indexed.":"Sākotnējie failu nosaukumi tiks saglabāti un indeksēti.","Original Name":"Sākotnējais nosaukums","Originals":"Oriģināli","Other":"Citi","Our mission is to provide the most user- and privacy-friendly solution to keep your pictures organized and accessible.":"Mūsu misija ir nodrošināt lietotājam un privātumam draudzīgāko risinājumu, lai jūsu attēli būtu sakārtoti un pieejami.","Outdoor":"Ārā","Page {0}, Current page":"{0}. lapa, pašreizējā lapa","Pages":"Lapas","Pagination Navigation":"Lappušu navigācija","Panorama":"Panorāma","Panoramas":"Panorāmas","Part of all selected photos":"Daļa no visām atlasītajām fotogrāfijām","Part of some selected photos":"Daļa no dažām atlasītajām fotogrāfijām","Password":"Parole","Password changed":"Parole nomainīta","People":"Cilvēki","People you share a link with will be able to view public contents.":"Personas, ar kurām kopīgojat saiti, varēs skatīt publisko saturu.","Permanently deleted":"Neatgriezeniski dzēsts","Person":"Persona","Person cover updated":"Personas vāks atjaunināts","Person not found":"Persona nav atrasta","Personal":"Personīgā","Photo":"Foto","PhotoPrism is 100% self-funded and independent.":"PhotoPrism ir 100% pašfinansēts un neatkarīgs.","PhotoPrism+ Membership":"PhotoPrism+ dalība","Photos":"Fotogrāfijas","Picture":"Attēls","Picture Title":"Attēla nosaukums","Pink":"Rozā","Place":"Vieta","Place & Time":"Vieta un laiks","Places":"Vietas","Please confirm your new password.":"Lūdzu, apstipriniet savu jauno paroli.","Please copy the following randomly generated app password and keep it in a safe place, as you will not be able to see it again:":"Lūdzu, nokopējiet šo nejauši ģenerēto lietotnes paroli un glabājiet to drošā vietā, jo jūs to vairs nevarēsiet redzēt:","Please do not upload any private, unlawful or offensive pictures.":"Lūdzu, neaugšupielādējiet privātus, nelikumīgus vai aizskarošus attēlus.","Please don't upload photos containing offensive content.":"Lūdzu, neaugšupielādējiet fotoattēlus ar aizskarošu saturu.","Please enter OTP character {0}":"Lūdzu, ievadiet vienreizējās paroles rakstzīmi {0}","Please note that changing your password will log you out on other devices and browsers.":"Lūdzu, ņemiet vērā, ka, mainot paroli, jūs tiksiet izrakstīts no citām ierīcēm un pārlūkprogrammām.","Please restart your instance for the changes to take effect.":"Lūdzu, restartējiet instanci, lai izmaiņas stātos spēkā.","Please wait…":"Lūdzu, uzgaidiet…","PM":"PM","PNG Size Limit: %{n}px":"PNG izmēra ierobežojums: %{n} pikseļi","Portal":"Portāls","Portrait":"Portrets","Preserve filenames":"Saglabāt failu nosaukumus","Press enter to create a new album.":"Lai izveidotu jaunu albumu, nospiediet taustiņu Enter.","Press enter to create new item":"Nospiediet taustiņu Enter, lai izveidotu jaunu vienumu","Prevent database and album backups as well as YAML sidecar files from being created.":"Novērst datubāzes un albuma dublējumu, kā arī YAML blakusfailu izveidi.","Prevent other apps from accessing PhotoPrism as a shared network drive.":"Neļaujiet citām lietotnēm piekļūt PhotoPrism kā koplietojamam tīkla diskam.","Preview":"Priekšskatījums","Preview Images":"Priekšskatīt attēlus","Previous":"Iepriekšējais","Previous page":"Iepriekšējā lapa","Previous visual":"Iepriekšējais vizuālais materiāls","Primary":"Primārā","Private":"Privāts","Product Feedback":"Produkta atsauksmes","Projection":"Projekcija","Purple":"Violets","Quality Filter":"Kvalitātes filtrs","Quality Score":"Kvalitātes rādītājs","Random":"Nejauši","Rating {0} of {1}":"Vērtējums {0} no {1}","Raw":"Neapstrādāts","RAW":"RAW","Re-index all originals, including already indexed and unchanged files.":"Atkārtoti indeksēt visus oriģinālus, tostarp jau indeksētos un nemainītos failus.","Read-Only Mode":"Tikai lasīšanas režīms","Recently Added":"Nesen pievienots","Recently Archived":"Nesen arhivēti","Recently Edited":"Nesen rediģēts","Recognition starts after indexing has been completed.":"Atpazīšana sākas pēc indeksēšanas pabeigšanas.","Recognized":"Atzīt","Recovery Code":"Atkopšanas kods","Red":"Sarkans","Refresh":"Atsvaidzināt","Regions":"Reģioni","Register":"Reģistrēties","Reload":"Pārlādēt","Reloading…":"Atkārtota ielāde…","Remote Sync":"Attālā sinhronizācija","Remove":"Noņemt","remove failed: unknown album":"Neizdevās noņemt: nezināms albums","Remove from Album":"Noņemt no albuma","Remove from all":"Noņemt no visiem","Remove from all selected photos":"Noņemt no visiem atlasītajiem fotoattēliem","Remove imported files to save storage. Unsupported file types will never be deleted, they remain in their current location.":"Noņemiet importētos failus, lai ietaupītu vietu krātuvē. Neatbalstīti failu tipi nekad netiks dzēsti, tie paliks to pašreizējā atrašanās vietā.","Remove the selected instance from the cluster registry?":"Vai noņemt atlasīto instanci no klastera reģistra?","Removed":"Noņemts","Repeated":"Atkārtots","Request failed - are you offline?":"Pieprasījums neizdevās. Vai esat bezsaistē?","Request failed - invalid response":"Pieprasījums neizdevās — nederīga atbilde","Require non-photographic and low-quality images to be reviewed before they appear in search results.":"Pieprasīt, lai attēli, kas nav fotoattēli un ir zemas kvalitātes, tiktu pārskatīti, pirms tie parādās meklēšanas rezultātos.","Resolution":"Izšķirtspēja","Restart":"Restartēt","Restore":"Atjaunot","Restored":"Atjaunots","Retry Limit":"Atkārtotu mēģinājumu limits","Retype Password":"Atkārtojiet paroli","Review":"Atsauksme","Role":"Loma","Roles":"Lomas","Rotated":"Pagriezts","Rows per page:":"Rindas lapā:","Satellite":"Satelīts","Save":"Saglabāt","Save changes":"Saglabāt izmaiņas","Scan":"Skenēt","Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated verification code:":"Noskenējiet QR kodu ar autentifikācijas lietotni vai izmantojiet tālāk norādīto iestatīšanas atslēgu un pēc tam ievadiet ģenerēto verifikācijas kodu:","Scans":"Skenējumi","Scope":"Darbības joma","Scopes":"Darbības jomas","Search":"Meklēt","Season":"Sezona","Secret":"Noslēpums","Security and Access":"Drošība un piekļuve","Select":"Atlasīt","Select a source folder to import files…":"Atlasiet avota mapi, lai importētu failus…","Select date":"Izvēlieties datumu","Select dates":"Izvēlieties datumus","Select or create albums":"Atlasīt vai izveidot albumus","Select or create labels":"Atlasiet vai izveidojiet etiķetes","Select the files to upload…":"Atlasiet augšupielādējamos failus…","Select the folder to be indexed…":"Atlasiet mapi, kas jāindeksē…","Select Time":"Izvēlieties laiku","Selection":"Atlase","Selection approved":"Atlase apstiprināta","Selection archived":"Atlase arhivēta","Selection restored":"Atlase atjaunota","Sequential Name":"Secīgs nosaukums","Service":"Pakalpojums","Service URL":"Pakalpojuma URL","Services":"Pakalpojumi","Session":"Sesija","Session ID":"Sesijas ID","Sessions":"Sesijas","Set as Album Cover":"Iestatīt kā albuma vāku","Set as Cover Image":"Iestatīt kā vāka attēlu","Settings":"Iestatījumi","Settings saved":"Iestatījumi saglabāti","Setup":"Iestatīšana","Severity":"Smagums","Share":"Kopīgot","Share %{s}":"Kopīgot %{s}","Show":"Rādīt","Show All Labels":"Rādīt visas etiķetes","Show all new faces":"Rādīt visas jaunās sejas","Show Captions":"Rādīt parakstus","Show hidden":"Rādīt paslēptos","Show Important Only":"Rādīt tikai svarīgos","Show logs in the web interface to monitor activity and troubleshoot problems.":"Rādīt žurnālus tīmekļa saskarnē, lai uzraudzītu aktivitātes un novērstu problēmas.","Show smart albums that group pictures by occasion, trip, or location.":"Rādīt viedos albumus, kas grupē attēlus pēc notikuma, ceļojuma vai atrašanās vietas.","Show the Account page so users can manage their profile and security settings.":"Rādīt konta lapu, lai lietotāji varētu pārvaldīt savu profilu un drošības iestatījumus.","Show the Calendar view to browse the library by year and month.":"Rādīt kalendāra skatu, lai pārlūkotu bibliotēku pēc gada un mēneša.","Show the Labels section to view and manage AI-generated labels.":"Rādīt sadaļu Etiķetes, lai skatītu un pārvaldītu mākslīgā intelekta ģenerētās etiķetes.","Show the Library section to index, manage, and monitor the media library.":"Rādīt sadaļu Bibliotēka, lai indeksētu, pārvaldītu un pārraudzītu multivides bibliotēku.","Show the Places view with interactive maps so you can browse photos by location.":"Rādīt vietu skatu ar interaktīvām kartēm, lai varētu pārlūkot fotoattēlus pēc atrašanās vietas.","Show Titles":"Rādīt nosaukumus","Sidecar":"Blakusvāģis","Sidecar Files":"Blakusvāģu faili","Sign in":"Pierakstīties","Similar":"Līdzīgi","Site URL":"Vietnes URL","Size":"Izmērs","Slideshow":"Slaidrāde","Slow":"Lēns","Software":"Programmatūra","Software Update":"Programmatūras atjauninājums","Some albums could not be copied":"Dažus albumus nevarēja nokopēt","Some albums could not be created. Please edit the names and try again.":"Dažus albumus nevarēja izveidot. Lūdzu, rediģējiet nosaukumus un mēģiniet vēlreiz.","Some albums could not be updated":"Dažus albumus nevarēja atjaunināt","Something went wrong, try again":"Kaut kas nogāja greizi, mēģiniet vēlreiz","Sort by":"Kārtot pēc","Sort by Name (A–Z)":"Kārtot pēc nosaukuma (A–Z)","Sort by Photo Count":"Kārtot pēc fotoattēlu skaita","Sort by Relevance":"Kārtot pēc atbilstības","Sort Order":"Kārtošanas secība","Sorted ascending.":"Kārtots augošā secībā.","Sorted descending.":"Kārtots dilstošā secībā.","Source":"Avots","Stack":"Kaudze","Stack files sharing the same unique image or instance identifier.":"Sakraut failus, kuriem ir viens un tas pats unikālais attēls vai instances identifikators.","Stack pictures taken at the exact same time and location based on their metadata.":"Apvienojiet attēlus, kas uzņemti vienā un tajā pašā laikā un vietā, pamatojoties uz to metadatiem.","Stackable":"Sakraujams","Stacks":"Skursteņi","Start":"Sākt","Start Page":"Sākumlapa","Static Size Limit: %{n}px":"Statiskā izmēra ierobežojums: %{n}px","Status":"Statuss","Storage":"Uzglabāšana","Streets":"Ielas","Subject":"Tēma","Submit":"Iesniegt","Successfully activated":"Veiksmīgi aktivizēts","Successfully Connected":"Veiksmīgi izveidots savienojums","Successfully deleted":"Veiksmīgi dzēsts","Successfully verified":"Veiksmīgi verificēts","Super Admin":"Superadministrators","Support for additional services, like Google Drive, will be added over time.":"Atbalsts papildu pakalpojumiem, piemēram, Google diskam, tiks pievienots laika gaitā.","Support Our Mission":"Atbalstiet mūsu misiju","Sync":"Sinhronizācija","Sync raw and video files":"Sinhronizēt neapstrādātus un video failus","Taken":"Paņemts","Teal":"Zilganzaļa","Terrain":"Reljefs","Text":"Teksts","Text too long":"Teksts ir pārāk garš","Thank You for Your Support!":"Paldies par jūsu atbalstu!","That's why PhotoPrism was built from the ground up to run wherever you need it, without compromising freedom, privacy, or functionality.":"Tāpēc PhotoPrism tika izstrādāts no paša sākuma, lai darbotos jebkur, kur jums tas nepieciešams, neapdraudot brīvību, privātumu vai funkcionalitāti.","The index currently contains %{n} hidden files.":"Indeksā pašlaik ir %{n} slēpti faili.","Their format may not be supported, they haven't been converted to JPEG yet or there are duplicates.":"To formāts var netikt atbalstīts, tie vēl nav konvertēti uz JPEG vai arī ir dublikāti.","Theme":"Tēma","This field is required":"Šis lauks ir obligāts","This mounts the originals folder as a network drive and allows you to open, edit, and delete files from your computer or smartphone as if they were local.":"Tas pievieno oriģinālu mapi kā tīkla disku un ļauj atvērt, rediģēt un dzēst failus no datora vai viedtālruņa tā, it kā tie būtu lokāli.","This mounts the originals folder as a network drive and allows you to open, edit, and delete files from your computer or smartphone as if they were local. ":"Tas pievieno oriģinālu mapi kā tīkla disku un ļauj atvērt, rediģēt un dzēst failus no datora vai viedtālruņa tā, it kā tie būtu lokāli. ","Time":"Laiks","Time UTC":"Laiks UTC","Time Zone":"Laika josla","Timeout":"Taimauts","Title":{"":"Nosaukums","Account":"Nosaukums","Photo":"Nosaukums"},"Title / Position":"Amats/Amats","Title too long":"Nosaukums ir pārāk garš","to":"uz","To avoid being locked out of your account, please download, print or copy this recovery code now and keep it in a safe place.":"Lai izvairītos no konta bloķēšanas, lūdzu, lejupielādējiet, izdrukājiet vai kopējiet šo atkopšanas kodu tūlīt un glabājiet to drošā vietā.","To generate a new app-specific password, please enter the name and authorization scope of the application and select an expiration date:":"Lai ģenerētu jaunu lietotnei paredzētu paroli, lūdzu, ievadiet lietojumprogrammas nosaukumu un autorizācijas darbības jomu un atlasiet derīguma termiņu:","To switch to a new authenticator app or device, first deactivate two-factor authentication and then reactivate it:":"Lai pārslēgtos uz jaunu autentifikatora lietotni vai ierīci, vispirms deaktivizējiet divfaktoru autentifikāciju un pēc tam to atkārtoti aktivizējiet:","To upgrade, you can either enter an activation code or click \"Register\" to sign up on our website:":"Lai jauninātu, varat ievadīt aktivizācijas kodu vai noklikšķināt uz \"Reģistrēties\", lai reģistrētos mūsu tīmekļa vietnē:","Today":"Šodien","Toggle View":"Pārslēgt skatu","Token":"Žetons","Too many files selected":"Atlasīts pārāk daudz failu","Too many requests":"Pārāk daudz pieprasījumu","Troubleshooting":"Problēmu novēršana","Try again using other filters or keywords.":"Mēģiniet vēlreiz, izmantojot citus filtrus vai atslēgvārdus.","Two-factor authentication has been enabled for your account.":"Jūsu kontam ir iespējota divfaktoru autentifikācija.","Type":"Tips","Unable to delete":"Nevar izdzēst","Unauthorized":"Neautorizēts","Undo":"Atsaukt","Unique ID":"Unikāls ID","Unknown":"Nezināms","Unregistered":"Nereģistrēts","Unsorted":"Nešķirots","Unstack":"Atdalīt","Updated":"Atjaunināts","Updating faces":"Seju atjaunināšana","Updating index":"Indeksa atjaunināšana","Updating moments":"Atjaunināšanas mirkļi","Updating picture…":"Notiek attēla atjaunināšana…","Updating previews":"Priekšskatījumu atjaunināšana","Updating stacks":"Kaudzīšu atjaunināšana","Upgrade":"Jaunināt","Upgrade Now":"Jaunināt tūlīt","Upload":"Augšupielādēt","Upload complete. Indexing…":"Augšupielāde pabeigta. Indeksēšana…","Upload failed":"Augšupielāde neizdevās","Upload local files":"Augšupielādēt lokālos failus","Upload Path":"Augšupielādes ceļš","Uploading %{n} of %{t}…":"Notiek %{n} no %{t} augšupielāde…","Uploading photos…":"Notiek fotoattēlu augšupielāde…","Uploading…":"Notiek augšupielāde…","Uploads that may contain such images will be rejected automatically.":"Augšupielādes, kas var saturēt šādus attēlus, tiks automātiski noraidītas.","Use Presets":"Izmantojiet sākotnējos iestatījumus","Use the following recovery code to access your account when you are unable to generate a valid verification code with your authenticator app:":"Ja autentifikācijas lietotnē nevarat ģenerēt derīgu verifikācijas kodu, izmantojiet šo atkopšanas kodu, lai piekļūtu savam kontam:","Use your recovery code or contact an administrator for help.":"Izmantojiet savu atkopšanas kodu vai sazinieties ar administratoru, lai saņemtu palīdzību.","User":"Lietotājs","User Guide":"Lietotāja rokasgrāmata","User Interface":"Lietotāja saskarne","Username":"Lietotājvārds","Users":"Lietotāji","Vector":"Vektors","Vectors":"Vektori","Verification Code":"Verifikācijas kods","Version":"Versija","Video":"Video","Video Duration":"Video ilgums","Videos":"Video","View":"Skatīt","View search results as a list.":"Skatīt meklēšanas rezultātus kā sarakstu.","Viewer":"Skatītājs","Vision":"Vīzija","Visitor":"Apmeklētājs","Visual Similarity":"Vizuālā līdzība","Warning":"Brīdinājums","We appreciate your feedback!":"Mēs novērtējam jūsu atsauksmes!","We do our best to respond within five business days or less.":"Mēs darām visu iespējamo, lai atbildētu piecu darba dienu laikā vai ātrāk.","Web Login":"Tīmekļa pieteikšanās","WebDAV":"WebDAV","WebDAV clients can connect to PhotoPrism using the following URL:":"WebDAV klienti var izveidot savienojumu ar PhotoPrism, izmantojot šo URL:","WebDAV clients, like Microsoft’s Windows Explorer or Apple's Finder, can connect directly to PhotoPrism. ":"WebDAV klienti, piemēram, Microsoft Windows Explorer vai Apple Finder, var tieši izveidot savienojumu ar PhotoPrism. ","WebDAV Upload":"WebDAV augšupielāde","WebGL support is disabled in your browser":"Jūsu pārlūkprogrammā ir atspējots WebGL atbalsts.","Website":"Tīmekļa vietne","White":"Balts","Work Details":"Darba detaļas","Year":"Gads","Yellow":"Dzeltens","Yes":"Jā","You are welcome to contact us at membership@photoprism.app for questions regarding your membership.":"Ja jums ir jautājumi par savu dalību, lūdzu, sazinieties ar mums pa e-pastu membership@photoprism.app.","You can only download one album":"Varat lejupielādēt tikai vienu albumu","You can only download one label":"Varat lejupielādēt tikai vienu etiķeti","You can search for a location or move the marker on the map to change the position:":"Varat meklēt atrašanās vietu vai pārvietot marķieri kartē, lai mainītu pozīciju:","You can upload up to %{n} files for test purposes.":"Testēšanas nolūkos varat augšupielādēt līdz %{n} failiem.","You may only select one item":"Varat izvēlēties tikai vienu vienumu","You may rescan your library to find additional faces.":"Varat atkārtoti skenēt savu bibliotēku, lai atrastu papildu sejas.","Your account has been successfully connected.":"Jūsu konts ir veiksmīgi pievienots.","Your browser does not support WebGL":"Jūsu pārlūkprogramma neatbalsta WebGL","Your continued support helps us provide regular updates and remain independent, so we can fulfill our mission and protect your privacy.":"Jūsu pastāvīgais atbalsts palīdz mums regulāri atjaunināt informāciju un saglabāt neatkarību, lai mēs varētu pildīt savu misiju un aizsargāt jūsu privātumu.","Your library is continuously analyzed to automatically create albums of special moments, trips, and places.":"Jūsu bibliotēka tiek nepārtraukti analizēta, lai automātiski izveidotu albumus ar īpašiem mirkļiem, ceļojumiem un vietām.","Zoom in/out":"Pietuvināt/attālināt","Archive":{"Noun":"Arhīvs","Verb":"Arhīvēt"}}} \ No newline at end of file diff --git a/frontend/src/locales/lv.po b/frontend/src/locales/lv.po new file mode 100644 index 000000000..1f1ef63c3 --- /dev/null +++ b/frontend/src/locales/lv.po @@ -0,0 +1,4775 @@ +msgid "" +msgstr "" +"Report-Msgid-Bugs-To: ci@photoprism.app\n" +"PO-Revision-Date: 2026-01-14 21:02+0000\n" +"Last-Translator: Janis Eglitis \n" +"Language-Team: none\n" +"Language: lv\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n % 10 == 0 || n % 100 >= 11 && n % 100 <= 19) ? 0 : ((n % 10 == 1 && n % 100 != 11) ? 1 : 2);\n" +"X-Generator: Weblate 5.15.1\n" + +#: src/locales.js:268 +msgid "{0} appended action" +msgstr "{0} pievienotā darbība" + +#: src/locales.js:272 +msgid "{0} files" +msgstr "{0} faili" + +#: src/locales.js:273 +msgid "{0} files ({1} in total)" +msgstr "{0} faili (kopā {1})" + +#: src/locales.js:262 +msgid "{0} more" +msgstr "{0} vairāk" + +#: src/locales.js:267 +msgid "{0} prepended action" +msgstr "{0} pievienota darbība" + +#: src/locales.js:242 +msgid "{0} selected" +msgstr "Atlasīts {0}" + +#: src/locales.js:236 +msgid "{0}-{1} of {2}" +msgstr "{0}-{1} no {2}" + +#: src/page/albums.vue:1177 +msgid "%{n} albums found" +msgstr "Atrasti %{n} albumi" + +#: src/component/service/upload.vue:190 +msgid "%{n} files uploaded" +msgstr "Augšupielādēti %{n} faili" + +#: src/page/library/browse.vue:483 +msgid "%{n} folders found" +msgstr "Atrastas %{n} mapes" + +#: src/component/navigation.vue:816 +msgid "%{n} GB of %{q} GB used" +msgstr "Izmantots %{n} GB no %{q} GB" + +#: src/page/labels.vue:970 +msgid "%{n} labels found" +msgstr "Atrastas %{n} birkas" + +#: src/page/people/new.vue:495 +#: src/page/people/new.vue:601 +#: src/page/people/recognized.vue:744 +msgid "%{n} people found" +msgstr "Atrasti %{n} cilvēki" + +#: src/page/album/photos.vue:382 +#: src/page/album/photos.vue:575 +#: src/page/photos.vue:516 +#: src/page/photos.vue:713 +msgid "%{n} pictures found" +msgstr "Atrasti %{n} attēli" + +#: src/common/form.js:291 +#: src/common/form.js:296 +msgid "%{s} is too long" +msgstr "%{s} ir pārāk garš" + +#: src/common/form.js:290 +#: src/common/form.js:295 +msgid "%{s} is too short" +msgstr "%{s} ir pārāk īss" + +#: src/options/options.js:423 +msgid "1 hour" +msgstr "1 stunda" + +#: src/options/options.js:425 +msgid "12 hours" +msgstr "12 stundas" + +#: src/component/user/edit/dialog.vue:304 +#: src/page/settings/account.vue:168 +#: src/component/settings/passcode.vue:25 +#: src/page/settings/account.vue:168 +msgid "2-Factor Authentication" +msgstr "Divfaktoru autentifikācija" + +#: src/component/user/edit/dialog.vue:303 +#: src/options/auth.js:47 +msgid "2FA" +msgstr "2FA" + +#: src/options/options.js:424 +msgid "4 hours" +msgstr "4 stundas" + +#: src/component/share/dialog.vue:132 +msgid "A click will copy it to your clipboard." +msgstr "Klikšķis nokopēs to starpliktuvē." + +#: src/component/update.vue:53 +msgid "A new version of %{s} is available:" +msgstr "Ir pieejama jauna %{s} versija:" + +#: src/component/user/edit/dialog.vue:135 +#: src/component/user/edit/dialog.vue:136 +#: src/page/about/about.vue:19 +#: src/page/settings/account.vue:139 +#: src/page/settings/account.vue:140 +#: src/app/routes.js:64 +#: src/component/navigation.vue:424 +#: src/component/navigation.vue:425 +#: src/component/navigation.vue:449 +#: src/component/navigation.vue:455 +#: src/component/navigation.vue:470 +#: src/component/navigation.vue:701 +#: src/component/navigation.vue:709 +#: src/component/navigation.vue:740 +#: src/component/navigation.vue:747 +#: src/page/people/recognized.vue:169 +#: src/page/settings/account.vue:139 +#: src/page/settings/account.vue:140 +msgid "About" +msgstr "Par programmu" + +#: src/options/auth.js:27 +#: src/options/auth.js:45 +msgid "Access Token" +msgstr "Piekļuves žetons" + +#: src/component/navigation.vue:1068 +#: src/component/navigation.vue:868 +#: src/component/service/upload.vue:25 +#: src/locales.js:331 +#: src/model/service.js:72 +#: src/model/user.js:151 +#: src/page/settings.vue:112 +#: src/page/settings/general.vue:334 +msgid "Account" +msgstr "Konts" + +#: src/component/user/add/dialog.vue:280 +#: src/component/service/add.vue:134 +msgid "Account created" +msgstr "Konts izveidots" + +#: src/component/user/remove/dialog.vue:61 +#: src/component/service/remove.vue:73 +msgid "Account deleted" +msgstr "Konts ir dzēsts" + +#: src/page/admin/users.vue:530 +msgid "Account restored" +msgstr "Konts ir atjaunots" + +#: src/page/admin.vue:54 +#: src/locales.js:332 +msgid "Accounts" +msgstr "Konti" + +#: src/component/photo/edit/info.vue:198 +#: src/component/photo/edit/info.vue:199 +msgid "Accuracy" +msgstr "Precizitāte" + +#: src/component/photo/edit/labels.vue:227 +#: src/component/photo/edit/labels.vue:66 +msgid "Action" +msgstr "Darbība" + +#: src/component/photo/edit/files.vue:38 +msgid "Actions" +msgstr "Darbības" + +#: src/page/connect.vue:100 +#: src/page/connect.vue:135 +#: src/page/connect.vue:142 +#: src/page/connect.vue:167 +#: src/page/connect.vue:92 +#: src/page/connect.vue:93 +#: src/component/settings/passcode.vue:107 +#: src/component/settings/passcode.vue:108 +#: src/component/settings/passcode.vue:117 +#: src/component/settings/passcode.vue:124 +#: src/component/settings/passcode.vue:162 +#: src/component/settings/passcode.vue:174 +#: src/page/connect.vue:111 +#: src/page/connect.vue:146 +#: src/page/connect.vue:152 +#: src/page/connect.vue:177 +#: src/page/connect.vue:98 +#: src/page/connect.vue:99 +msgid "Activate" +msgstr "Aktivizēt" + +#: src/locales.js:223 +msgid "Activate to remove sorting." +msgstr "Aktivizējiet, lai noņemtu kārtošanu." + +#: src/locales.js:225 +msgid "Activate to sort ascending." +msgstr "Aktivizējiet, lai kārtotu augošā secībā." + +#: src/locales.js:224 +msgid "Activate to sort descending." +msgstr "Aktivizējiet, lai kārtotu dilstošā secībā." + +#: src/page/connect.vue:125 +#: src/page/connect.vue:136 +msgid "Activation Code" +msgstr "Aktivizācijas kods" + +#: src/page/admin.vue:76 +#: src/locales.js:348 +msgid "Activity" +msgstr "Aktivitāte" + +#: src/component/user/add/dialog.vue:204 +#: src/page/admin/users.vue:36 +#: src/component/settings/apps.vue:127 +#: src/component/settings/apps.vue:128 +#: src/component/settings/apps.vue:137 +#: src/component/settings/apps.vue:144 +#: src/component/settings/apps.vue:222 +#: src/component/settings/apps.vue:240 +msgid "Add" +msgstr "Pievienot" + +#: src/component/photo/edit/people.vue:106 +#: src/page/people/new.vue:127 +msgid "Add %{s}?" +msgstr "Pievienot %{s}?" + +#: src/component/user/add/dialog.vue:16 +#: src/component/service/add.vue:17 +msgid "Add Account" +msgstr "Pievienot kontu" + +#: src/page/albums.vue:186 +#: src/page/albums.vue:189 +#: src/page/albums.vue:50 +msgid "Add Album" +msgstr "Pievienot albumu" + +#: src/component/share/dialog.vue:19 +msgid "Add Link" +msgstr "Pievienot saiti" + +#: src/component/photo/edit/people.vue:106 +#: src/component/photo/edit/people.vue:142 +#: src/page/people/new.vue:127 +#: src/page/people/new.vue:192 +msgid "Add person?" +msgstr "Pievienot personu?" + +#: src/page/albums.vue:262 +#: src/page/albums.vue:263 +#: src/page/albums.vue:273 +#: src/page/albums.vue:275 +#: src/page/albums.vue:292 +msgid "Add pictures from search results by selecting them." +msgstr "Pievienojiet attēlus no meklēšanas rezultātiem, tos atlasot." + +#: src/component/album/clipboard.vue:65 +#: src/component/file/clipboard.vue:44 +#: src/component/label/clipboard.vue:32 +#: src/component/people/clipboard.vue:42 +#: src/component/photo/album/dialog.vue:101 +#: src/component/photo/album/dialog.vue:16 +#: src/component/photo/clipboard.vue:104 +msgid "Add to album" +msgstr "Pievienot albumam" + +#: src/component/input/chip-selector.vue:182 +msgid "Add to all" +msgstr "Pievienot visiem" + +#: src/component/input/chip-selector.vue:182 +msgid "Add to all selected photos" +msgstr "Pievienot visiem atlasītajiem fotoattēliem" + +#: src/component/photo/edit/files.vue:276 +#: src/component/photo/edit/files.vue:277 +#: src/component/photo/edit/files.vue:283 +msgid "Added" +msgstr "Pievienots" + +#: src/component/location/dialog.vue:21 +#: src/component/location/dialog.vue:26 +#: src/component/photo/batch-edit.vue:318 +#: src/component/photo/edit/details.vue:158 +msgid "Adjust Location" +msgstr "Pielāgot atrašanās vietu" + +#: src/options/admin.js:6 +#: src/common/util.js:818 +#: src/options/auth.js:6 +msgid "Admin" +msgstr "Administrators" + +#: src/page/settings.vue:86 +msgid "Advanced" +msgstr "Paplašināts" + +#: src/options/options.js:433 +msgid "After 1 day" +msgstr "Pēc 1 dienas" + +#: src/options/options.js:434 +msgid "After 3 days" +msgstr "Pēc 3 dienām" + +#: src/options/options.js:435 +msgid "After 7 days" +msgstr "Pēc 7 dienām" + +#: src/component/settings/passcode.vue:33 +msgid "After entering your password for confirmation, you can set up two-factor authentication with a compatible authenticator app or device:" +msgstr "Pēc paroles ievadīšanas apstiprināšanai varat iestatīt divfaktoru autentifikāciju, izmantojot saderīgu autentifikācijas lietotni vai ierīci:" + +#: src/options/options.js:437 +msgid "After one month" +msgstr "Pēc viena mēneša" + +#: src/options/options.js:439 +msgid "After one year" +msgstr "Pēc viena gada" + +#: src/page/albums.vue:169 +msgid "After selecting pictures from search results, you can add them to an album using the context menu." +msgstr "Pēc attēlu atlasīšanas meklēšanas rezultātos tos var pievienot albumam, izmantojot konteksta izvēlni." + +#: src/options/options.js:438 +msgid "After two months" +msgstr "Pēc diviem mēnešiem" + +#: src/options/options.js:436 +msgid "After two weeks" +msgstr "Pēc divām nedēļām" + +#: src/model/album.js:205 +msgid "Album" +msgstr "Albums" + +#: src/page/settings/advanced.vue:189 +msgid "Album Backups" +msgstr "Albuma dublējumkopijas" + +#: src/page/albums.vue:1265 +msgid "Album created" +msgstr "Albums izveidots" + +#: src/page/album/photos.vue:660 +msgid "Album deleted" +msgstr "Albums ir izdzēsts" + +#: src/app/routes.js:205 +#: src/component/album/toolbar.vue:135 +#: src/component/navigation.vue:268 +#: src/component/navigation.vue:909 +#: src/component/photo/batch-edit.vue:474 +#: src/component/photo/edit/info.vue:89 +#: src/component/photo/edit/info.vue:90 +#: src/options/options.js:257 +msgid "Albums" +msgstr "Albumi" + +#: src/component/album/clipboard.vue:218 +msgid "Albums deleted" +msgstr "Albumi ir dzēsti" + +#: src/locales.js:218 +#: src/locales.js:231 +msgid "All" +msgstr "Visi" + +#: src/page/albums.vue:1000 +msgid "All %{n} albums loaded" +msgstr "Visi %{n} albumi ir ielādēti" + +#: src/page/labels.vue:772 +msgid "All %{n} labels loaded" +msgstr "Visas %{n} etiķetes ir ielādētas" + +#: src/page/people/recognized.vue:574 +msgid "All %{n} people loaded" +msgstr "Visi %{n} cilvēki ielādēti" + +#: src/component/photo/toolbar.vue:367 +msgid "All Cameras" +msgstr "Visas kameras" + +#: src/component/photo/toolbar.vue:370 +#: src/page/albums.vue:1337 +#: src/page/albums.vue:401 +#: src/page/albums.vue:613 +msgid "All Categories" +msgstr "Visas kategorijas" + +#: src/component/photo/toolbar.vue:369 +msgid "All Colors" +msgstr "Visas krāsas" + +#: src/component/photo/toolbar.vue:366 +msgid "All Countries" +msgstr "Visas valstis" + +#: src/page/about/feedback.vue:154 +#: src/page/about/feedback.vue:146 +msgid "All fields are required" +msgstr "Visi lauki ir obligāti jāaizpilda" + +#: src/page/library/import.vue:111 +msgid "All files from import folder" +msgstr "Visi faili no importēšanas mapes" + +#: src/component/photo/toolbar.vue:368 +msgid "All Lenses" +msgstr "Visi objektīvi" + +#: src/component/photo/toolbar.vue:371 +msgid "All Months" +msgstr "Visi mēneši" + +#: src/page/library/index.vue:107 +msgid "All originals" +msgstr "Visi oriģināli" + +#: src/component/photo/toolbar.vue:372 +#: src/page/albums.vue:455 +msgid "All Years" +msgstr "Visi gadi" + +#: src/page/settings/general.vue:246 +msgid "Allow configuration and use of connected apps and services for remote uploads and sync." +msgstr "Atļaut savienoto lietotņu un pakalpojumu konfigurēšanu un izmantošanu attālinātai augšupielādei un sinhronizācijai." + +#: src/page/settings/general.vue:201 +msgid "Allow editing of metadata such as title, description, date, and location." +msgstr "Atļaut rediģēt metadatus, piemēram, nosaukumu, aprakstu, datumu un atrašanās vietu." + +#: src/page/settings/general.vue:216 +msgid "Allow editing the metadata, labels, and albums of multiple pictures at once." +msgstr "Ļauj vienlaikus rediģēt vairāku attēlu metadatus, etiķetes un albumus." + +#: src/page/settings/general.vue:186 +msgid "Allow files to be copied or moved from the Import to the Originals folder." +msgstr "Atļaut failu kopēt vai pārvietot no importēšanas mapes uz oriģinālu mapi." + +#: src/page/settings/general.vue:276 +msgid "Allow files to be permanently deleted to free up storage space." +msgstr "Atļaujiet failu neatgriezenisku dzēšanu, lai atbrīvotu vietu krātuvē." + +#: src/page/settings/general.vue:261 +msgid "Allow users to archive photos and videos so they are hidden without being deleted." +msgstr "Ļaut lietotājiem arhivēt fotoattēlus un videoklipus, lai tie tiktu paslēpti, bet netiktu dzēsti." + +#: src/page/settings/general.vue:231 +msgid "Allow users to create and share links, and enable sharing with connected services." +msgstr "Atļaut lietotājiem izveidot un kopīgot saites un iespējot kopīgošanu ar pievienotajiem pakalpojumiem." + +#: src/page/settings/general.vue:156 +msgid "Allow users to upload new photos and videos through the web interface." +msgstr "Ļaut lietotājiem augšupielādēt jaunus fotoattēlus un videoklipus, izmantojot tīmekļa saskarni." + +#: src/component/share/dialog.vue:134 +msgid "Alternatively, you can upload files directly to WebDAV servers like Nextcloud." +msgstr "Varat arī augšupielādēt failus tieši WebDAV serveros, piemēram, Nextcloud." + +#: src/component/photo/edit/info.vue:192 +#: src/component/photo/edit/info.vue:193 +msgid "Altitude" +msgstr "Augstums" + +#: src/component/photo/batch-edit.vue:357 +#: src/component/photo/edit/details.vue:195 +msgid "Altitude (m)" +msgstr "Augstums (m)" + +#: src/locales.js:281 +msgid "AM" +msgstr "AM" + +#: src/app/routes.js:286 +#: src/component/photo/batch-edit.vue:164 +#: src/component/photo/batch-edit.vue:82 +#: src/component/photo/edit/files.vue:149 +#: src/component/photo/edit/files.vue:150 +#: src/component/photo/edit/files.vue:156 +#: src/component/photo/view/cards.vue:140 +#: src/component/photo/view/cards.vue:289 +#: src/component/photo/view/list.vue:95 +#: src/component/photo/view/mosaic.vue:87 +#: src/options/options.js:362 +msgid "Animated" +msgstr "Animēts" + +#: src/page/settings/general.vue:391 +msgid "Animation" +msgstr "Animācija" + +#: src/component/navigation.vue:205 +#: src/component/navigation.vue:206 +#: src/component/navigation.vue:230 +#: src/component/navigation.vue:236 +#: src/component/navigation.vue:251 +#: src/component/navigation.vue:345 +#: src/component/navigation.vue:351 +#: src/component/navigation.vue:357 +msgid "Animations" +msgstr "Animācijas" + +#: src/component/share/dialog.vue:133 +msgid "Any private photos and videos remain private and won't be shared." +msgstr "Visi privātie fotoattēli un videoklipi paliek privāti un netiks kopīgoti." + +#: src/component/service/edit.vue:192 +msgid "API Key" +msgstr "API atslēga" + +#: src/page/admin/sessions.vue:172 +#: src/locales.js:354 +#: src/options/auth.js:26 +msgid "Application" +msgstr "Programma" + +#: src/locales.js:355 +msgid "Applications" +msgstr "Programmas" + +#: src/component/photo/clipboard.vue:44 +#: src/component/photo/edit/details.vue:418 +#: src/component/photo/view/cards.vue:219 +msgid "Approve" +msgstr "Apstiprināt" + +#: src/component/photo/edit/details.vue:414 +msgid "Approve and save changes" +msgstr "Apstiprināt un saglabāt izmaiņas" + +#: src/page/settings/account.vue:183 +#: src/component/settings/apps.vue:25 +#: src/page/settings/account.vue:183 +msgid "Apps and Devices" +msgstr "Lietotnes un ierīces" + +#: src/component/lightbox.vue:2387 +#: src/component/photo/edit/info.vue:238 +#: src/component/photo/edit/info.vue:239 +msgid "Archived" +msgstr "Arhivēts" + +#: src/component/photo/archive/dialog.vue:15 +msgid "Are you sure you want to archive the selection?" +msgstr "Vai tiešām vēlaties arhivēt atlasi?" + +#: src/component/album/delete/dialog.vue:15 +msgid "Are you sure you want to delete these albums?" +msgstr "Vai tiešām vēlaties dzēst šos albumus?" + +#: src/component/label/delete/dialog.vue:15 +msgid "Are you sure you want to delete these labels?" +msgstr "Vai tiešām vēlaties dzēst šīs birkas?" + +#: src/component/user/remove/dialog.vue:15 +#: src/component/service/remove.vue:15 +msgid "Are you sure you want to delete this account?" +msgstr "Vai tiešām vēlaties dzēst šo kontu?" + +#: src/component/album/toolbar.vue:72 +msgid "Are you sure you want to delete this album?" +msgstr "Vai tiešām vēlaties dzēst šo albumu?" + +#: src/component/photo/clipboard.vue:167 +msgid "Are you sure you want to permanently delete these pictures?" +msgstr "Vai tiešām vēlaties neatgriezeniski dzēst šos attēlus?" + +#: src/component/file/delete/dialog.vue:15 +msgid "Are you sure you want to permanently delete this file?" +msgstr "Vai tiešām vēlaties neatgriezeniski dzēst šo failu?" + +#: src/component/session/remove/dialog.vue:15 +#: src/component/confirm/dialog.vue:19 +#: src/component/confirm/dialog.vue:5 +#: src/component/confirm/dialog.vue:6 +msgid "Are you sure?" +msgstr "Vai esi pārliecināts?" + +#: src/component/photo/batch-edit.vue:428 +#: src/component/photo/edit/details.vue:343 +msgid "Artist" +msgstr "Mākslinieks" + +#: src/component/photo/edit/files.vue:213 +#: src/component/photo/edit/files.vue:214 +#: src/component/photo/edit/files.vue:220 +msgid "Aspect Ratio" +msgstr "Malu attiecība" + +#: src/locales.js:338 +msgid "Attributes" +msgstr "Atribūti" + +#: src/app/routes.js:279 +#: src/component/navigation.vue:198 +#: src/component/navigation.vue:199 +#: src/component/navigation.vue:223 +#: src/component/navigation.vue:229 +#: src/component/navigation.vue:244 +#: src/component/navigation.vue:338 +#: src/component/navigation.vue:344 +#: src/options/options.js:358 +msgid "Audio" +msgstr "Audio" + +#: src/model/logs.js:184 +msgid "Audit Log" +msgstr "Audita žurnāls" + +#: src/component/user/add/dialog.vue:108 +#: src/component/user/edit/dialog.vue:184 +#: src/page/admin/sessions.vue:152 +#: src/page/admin/sessions.vue:292 +#: src/page/admin/users.vue:270 +#: src/locales.js:333 +msgid "Authentication" +msgstr "Autentifikācija" + +#: src/common/util.js:780 +msgid "Auto" +msgstr "Automātiski" + +#: src/component/settings/apps.vue:57 +msgid "Back" +msgstr "Atpakaļ" + +#: src/component/scroll.vue:5 +#: src/component/scroll.vue:6 +msgid "Back to top" +msgstr "Atpakaļ uz augšu" + +#: src/page/settings/advanced.vue:161 +msgid "Backup" +msgstr "Rezerves kopija" + +#: src/locales.js:204 +msgid "Badge" +msgstr "Žetons" + +#: src/component/user/edit/dialog.vue:245 +#: src/component/user/edit/dialog.vue:246 +msgid "Base Path" +msgstr "Saknes ceļš" + +#: src/common/util.js:812 +msgid "Batch" +msgstr "Partija" + +#: src/page/settings/general.vue:215 +msgid "Batch Edit" +msgstr "Partijas rediģēšana" + +#: src/page/about/about.vue:48 +#: src/page/connect.vue:114 +msgid "Become a member today, support our mission and enjoy our member benefits!" +msgstr "Kļūsti par biedru jau šodien, atbalsti mūsu misiju un izbaudi mūsu biedru priekšrocības!" + +#: src/component/confirm/sponsor.vue:23 +#: src/page/about/about.vue:56 +msgid "Being 100% self-funded and independent, we can promise you that we will never sell your data and that we will always be transparent about our software and services." +msgstr "Tā kā mēs esam 100% pašfinansēti un neatkarīgi, mēs varam jums apsolīt, ka nekad nepārdosim jūsu datus un vienmēr būsim caurspīdīgi attiecībā uz mūsu programmatūru un pakalpojumiem." + +#: src/component/user/edit/dialog.vue:152 +#: src/component/user/edit/dialog.vue:153 +#: src/page/settings/account.vue:123 +#: src/page/settings/account.vue:124 +#: src/page/settings/account.vue:123 +#: src/page/settings/account.vue:124 +msgid "Bio" +msgstr "Biogrāfija" + +#: src/page/settings/account.vue:212 +#: src/page/settings/account.vue:212 +msgid "Birth Date" +msgstr "Dzimšanas datums" + +#: src/options/options.js:458 +msgid "Black" +msgstr "Melns" + +#: src/options/options.js:486 +msgid "Blackman: Lanczos Modification, Less Ringing Artifacts" +msgstr "Blackman: Lanczos modifikācija, mazāk nevēlami efekti" + +#: src/options/options.js:454 +msgid "Blue" +msgstr "Zils" + +#: src/options/options.js:455 +msgid "Brown" +msgstr "Brūns" + +#: src/component/photo/edit/files.vue:107 +#: src/component/photo/edit/files.vue:67 +#: src/component/photo/edit/files.vue:75 +#: src/component/photo/toolbar.vue:94 +#: src/component/upload/dialog.vue:112 +#: src/component/upload/dialog.vue:70 +#: src/component/upload/dialog.vue:71 +#: src/component/upload/dialog.vue:82 +msgid "Browse" +msgstr "Pārlūkot" + +#: src/locales.js:278 +msgid "Browse Files" +msgstr "Pārlūkot failus" + +#: src/component/photo/edit/people.vue:232 +msgid "Browse Pictures" +msgstr "Pārlūkot attēlus" + +#: src/page/about/feedback.vue:112 +#: src/options/options.js:464 +msgid "Bug Report" +msgstr "Kļūdas ziņojums" + +#: src/page/connect.vue:8 +#: src/common/notify.js:119 +#: src/page/connect.vue:13 +msgid "Busy, please wait…" +msgstr "Aizņemts, lūdzu, uzgaidiet…" + +#: src/page/connect.vue:198 +msgid "By using the software and services we provide, you agree to our terms of service, privacy policy, and code of conduct." +msgstr "Izmantojot mūsu piedāvāto programmatūru un pakalpojumus, jūs piekrītat mūsu pakalpojumu sniegšanas noteikumiem, privātuma politikai un rīcības kodeksam." + +#: src/app/routes.js:218 +#: src/component/navigation.vue:506 +#: src/options/options.js:263 +#: src/page/settings/general.vue:96 +msgid "Calendar" +msgstr "Kalendārs" + +#: src/component/photo/edit/details.vue:210 +#: src/component/photo/toolbar.vue:132 +#: src/component/photo/view/cards.vue:253 +#: src/component/photo/view/list.vue:53 +msgid "Camera" +msgstr "Kamera" + +#: src/component/photo/edit/info.vue:117 +#: src/component/photo/edit/info.vue:118 +msgid "Camera Serial" +msgstr "Kameras sērijas numurs" + +#: src/page/auth/login.vue:138 +#: src/page/auth/login.vue:159 +#: src/page/auth/login.vue:46 +#: src/page/auth/login.vue:47 +#: src/page/auth/login.vue:54 +#: src/page/auth/login.vue:68 +msgid "Can't access your authenticator app or device?" +msgstr "Nevarat piekļūt autentifikatora lietotnei vai ierīcei?" + +#: src/page/album/photos.vue:391 +#: src/page/photos.vue:525 +msgid "Can't load more, limit reached" +msgstr "Nevar ielādēt vairāk, sasniegts ierobežojums" + +#: src/common/clipboard.js:102 +#: src/common/clipboard.js:139 +#: src/page/albums.vue:1275 +#: src/page/albums.vue:1291 +#: src/page/labels.vue:668 +#: src/page/labels.vue:688 +#: src/page/library/browse.vue:346 +#: src/page/library/browse.vue:362 +#: src/page/people/new.vue:423 +#: src/page/people/new.vue:439 +#: src/page/people/recognized.vue:506 +#: src/page/people/recognized.vue:522 +msgid "Can't select more items" +msgstr "Nevar atlasīt vairāk ierakstu" + +#: src/component/session/remove/dialog.vue:19 +#: src/component/user/add/dialog.vue:203 +#: src/component/user/edit/dialog.vue:312 +#: src/component/user/remove/dialog.vue:19 +#: src/page/connect.vue:46 +#: src/component/album/delete/dialog.vue:19 +#: src/component/album/edit/dialog.vue:109 +#: src/component/confirm/dialog.vue:23 +#: src/component/file/delete/dialog.vue:19 +#: src/component/label/delete/dialog.vue:19 +#: src/component/label/edit/dialog.vue:54 +#: src/component/location/dialog.vue:120 +#: src/component/people/edit/dialog.vue:64 +#: src/component/photo/album/dialog.vue:60 +#: src/component/service/add.vue:100 +#: src/component/service/edit.vue:243 +#: src/component/service/remove.vue:19 +#: src/component/service/upload.vue:57 +#: src/component/settings/apps.vue:173 +#: src/component/settings/passcode.vue:120 +#: src/component/settings/passcode.vue:171 +#: src/component/settings/password.vue:83 +#: src/component/share/dialog.vue:176 +#: src/locales.js:210 +#: src/page/auth/login.vue:127 +#: src/page/connect.vue:48 +#: src/page/library/import.vue:66 +#: src/page/library/index.vue:61 +msgid "Cancel" +msgstr "Atcelt" + +#: src/common/util.js:985 +#: src/common/util.js:992 +msgid "Cannot copy to clipboard" +msgstr "Nevar nokopēt starpliktuvē" + +#: src/common/util.js:800 +#: src/component/photo/batch-edit.vue:220 +#: src/component/photo/edit/details.vue:44 +#: src/component/photo/view/cards.vue:240 +#: src/component/sidebar/info.vue:25 +msgid "Caption" +msgstr "Paraksts" + +#: src/component/photo/toolbar.vue:396 +#: src/component/photo/toolbar.vue:402 +msgid "Cards" +msgstr "Kartītes" + +#: src/locales.js:258 +msgid "Carousel slide {0} of {1}" +msgstr "Karuseļa slaids {0} no {1}" + +#: src/page/about/feedback.vue:34 +#: src/page/about/feedback.vue:35 +#: src/component/album/edit/dialog.vue:70 +#: src/component/photo/toolbar.vue:263 +#: src/page/about/feedback.vue:35 +#: src/page/about/feedback.vue:36 +#: src/page/albums.vue:92 +msgid "Category" +msgstr "Kategorija" + +#: src/component/user/edit/dialog.vue:102 +#: src/page/settings/account.vue:107 +#: src/page/settings/account.vue:107 +msgid "Change Avatar" +msgstr "Mainīt avatāru" + +#: src/page/settings/account.vue:155 +#: src/component/settings/password.vue:16 +#: src/page/settings/account.vue:155 +msgid "Change Password" +msgstr "Mainīt paroli" + +#: src/component/photo/clipboard.vue:80 +msgid "Change private flag" +msgstr "Mainīt privāto atzīmi" + +#: src/component/user/add/dialog.vue:287 +#: src/component/user/add/dialog.vue:292 +#: src/component/user/edit/dialog.vue:421 +#: src/component/user/edit/dialog.vue:426 +#: src/page/settings/account.vue:539 +#: src/page/settings/account.vue:544 +#: src/page/settings/account.vue:472 +#: src/page/settings/account.vue:477 +msgid "Changes could not be saved" +msgstr "Izmaiņas nevarēja saglabāt" + +#: src/component/user/edit/dialog.vue:414 +#: src/component/user/edit/dialog.vue:490 +#: src/component/album/edit/dialog.vue:192 +#: src/component/label/edit/dialog.vue:110 +#: src/component/lightbox.vue:2346 +#: src/component/photo/batch-edit.vue:1428 +#: src/component/photo/edit/files.vue:570 +#: src/component/service/edit.vue:343 +#: src/component/share/dialog.vue:240 +#: src/component/share/dialog.vue:259 +#: src/page/albums.vue:1268 +#: src/page/people/recognized.vue:320 +#: src/page/settings/advanced.vue:535 +#: src/page/settings/content.vue:300 +#: src/page/settings/general.vue:515 +msgid "Changes successfully saved" +msgstr "Izmaiņas veiksmīgi saglabātas" + +#: src/page/settings/advanced.vue:16 +msgid "Changes to the advanced settings require a restart to take effect." +msgstr "Lai izmaiņas papildu iestatījumos stātos spēkā, ir nepieciešama restartēšana." + +#: src/component/photo/edit/info.vue:230 +#: src/component/photo/edit/info.vue:231 +msgid "Checked" +msgstr "Pārbaudīts" + +#: src/component/photo/edit/files.vue:260 +#: src/component/photo/edit/files.vue:261 +#: src/component/photo/edit/files.vue:267 +msgid "Chroma" +msgstr "Hroma" + +#: src/page/library/index.vue:266 +msgid "Cleaning index and cache" +msgstr "Notiek indeksa un kešatmiņas tīrīšana" + +#: src/page/library/index.vue:50 +msgid "Cleanup" +msgstr "Tīrīšana" + +#: src/locales.js:266 +msgid "Clear {0}" +msgstr "Notīrīt {0}" + +#: src/options/auth.js:12 +#: src/options/auth.js:24 +#: src/options/auth.js:44 +msgid "Client" +msgstr "Klients" + +#: src/options/auth.js:25 +msgid "Client Credentials" +msgstr "Klienta piekļuves dati" + +#: src/component/user/edit/dialog.vue:312 +#: src/page/admin/sessions.vue:224 +#: src/component/lightbox.vue:1247 +#: src/component/lightbox.vue:1248 +#: src/component/lightbox.vue:411 +#: src/component/photo/batch-edit.vue:513 +#: src/component/photo/edit/details.vue:403 +#: src/component/photo/edit/details.vue:408 +#: src/component/photo/toolbar.vue:97 +#: src/component/settings/apps.vue:100 +#: src/component/settings/apps.vue:237 +#: src/component/settings/passcode.vue:146 +#: src/component/settings/passcode.vue:147 +#: src/component/settings/passcode.vue:156 +#: src/component/settings/passcode.vue:163 +#: src/component/settings/passcode.vue:219 +#: src/component/settings/passcode.vue:235 +#: src/component/settings/passcode.vue:241 +#: src/component/settings/passcode.vue:256 +#: src/component/settings/passcode.vue:68 +#: src/component/settings/webdav.vue:80 +#: src/component/share/dialog.vue:142 +#: src/component/share/dialog.vue:72 +#: src/component/share/dialog.vue:73 +#: src/component/share/dialog.vue:82 +#: src/component/sidebar/info.vue:6 +#: src/component/update.vue:25 +#: src/component/upload/dialog.vue:109 +#: src/locales.js:206 +#: src/page/library/errors.vue:139 +msgid "Close" +msgstr "Aizvērt" + +#: src/app/routes.js:122 +msgid "Cluster" +msgstr "Klasteris" + +#: src/component/photo/edit/files.vue:157 +#: src/component/photo/edit/files.vue:158 +#: src/component/photo/edit/files.vue:164 +msgid "Codec" +msgstr "Kodeks" + +#: src/component/photo/toolbar.vue:243 +msgid "Color" +msgstr "Krāsa" + +#: src/component/photo/edit/files.vue:246 +#: src/component/photo/edit/files.vue:247 +#: src/component/photo/edit/files.vue:253 +msgid "Color Profile" +msgstr "Krāsu profils" + +#: src/page/discover.vue:12 +#: src/page/discover.vue:5 +msgid "Colors" +msgstr "Krāsas" + +#: src/page/connect.vue:127 +#: src/page/connect.vue:134 +#: src/page/connect.vue:143 +#: src/page/connect.vue:138 +#: src/page/connect.vue:144 +#: src/page/connect.vue:153 +msgid "Compare Editions" +msgstr "Salīdzināt izdevumus" + +#: src/page/library/index.vue:39 +msgid "Complete Rescan" +msgstr "Veikt pilnu pārlūkošanu" + +#: src/component/photo/edit/labels.vue:221 +#: src/component/photo/edit/labels.vue:57 +msgid "Confidence" +msgstr "Pārliecība" + +#: src/component/location/dialog.vue:123 +#: src/component/location/dialog.vue:62 +#: src/component/location/dialog.vue:63 +#: src/component/location/dialog.vue:75 +#: src/component/location/dialog.vue:82 +#: src/component/photo/album/dialog.vue:29 +#: src/component/photo/album/dialog.vue:30 +#: src/component/photo/album/dialog.vue:39 +#: src/component/photo/album/dialog.vue:63 +#: src/component/settings/passcode.vue:113 +#: src/component/settings/passcode.vue:123 +#: src/component/settings/passcode.vue:74 +#: src/component/settings/passcode.vue:75 +#: src/component/settings/passcode.vue:84 +#: src/component/settings/passcode.vue:91 +msgid "Confirm" +msgstr "Apstiprināt" + +#: src/component/service/add.vue:101 +#: src/page/settings/services.vue:105 +#: src/page/settings/services.vue:113 +#: src/page/settings/services.vue:56 +#: src/page/settings/services.vue:57 +#: src/page/settings/services.vue:99 +msgid "Connect" +msgstr "Savienot" + +#: src/page/settings/account.vue:196 +#: src/component/settings/webdav.vue:15 +#: src/page/settings/account.vue:196 +#: src/page/settings/services.vue:100 +msgid "Connect via WebDAV" +msgstr "Savienot caur WebDAV" + +#: src/page/connect.vue:281 +#: src/page/connect.vue:289 +msgid "Connected" +msgstr "Savienots" + +#: src/page/settings/account.vue:346 +#: src/page/settings/account.vue:279 +msgid "Contact Details" +msgstr "Kontaktinformācija" + +#: src/page/about/feedback.vue:10 +#: src/page/connect.vue:49 +#: src/page/about/feedback.vue:10 +#: src/page/connect.vue:51 +msgid "Contact Us" +msgstr "Sazinieties ar mums" + +#: src/page/albums.vue:259 +#: src/page/albums.vue:260 +#: src/page/albums.vue:269 +#: src/page/albums.vue:270 +#: src/page/albums.vue:280 +#: src/page/albums.vue:282 +#: src/page/albums.vue:303 +#: src/page/labels.vue:133 +#: src/page/labels.vue:142 +#: src/page/labels.vue:143 +#: src/page/labels.vue:145 +#: src/page/people/recognized.vue:118 +#: src/page/people/recognized.vue:127 +#: src/page/people/recognized.vue:131 +#: src/page/people/recognized.vue:177 +#: src/page/people/recognized.vue:80 +#: src/page/people/recognized.vue:81 +msgid "Contains %{n} pictures." +msgstr "Satur %{n} attēlus." + +#: src/page/albums.vue:257 +#: src/page/albums.vue:266 +#: src/page/albums.vue:267 +#: src/page/albums.vue:277 +#: src/page/albums.vue:279 +#: src/page/albums.vue:300 +#: src/page/labels.vue:139 +#: src/page/labels.vue:140 +#: src/page/labels.vue:142 +#: src/page/people/recognized.vue:115 +#: src/page/people/recognized.vue:124 +#: src/page/people/recognized.vue:128 +#: src/page/people/recognized.vue:174 +#: src/page/people/recognized.vue:77 +#: src/page/people/recognized.vue:78 +msgid "Contains one picture." +msgstr "Satur vienu attēlu." + +#: src/page/settings.vue:73 +msgid "Content" +msgstr "Saturs" + +#: src/component/settings/apps.vue:44 +#: src/component/settings/apps.vue:60 +msgid "Continue" +msgstr "Turpināt" + +#: src/options/admin.js:22 +#: src/options/auth.js:10 +msgid "Contributor" +msgstr "Dalībnieks" + +#: src/page/library/index.vue:276 +msgid "Converting" +msgstr "Konvertēšana" + +#: src/common/util.js:978 +msgid "Copied to clipboard" +msgstr "Kopēts starpliktuvē" + +#: src/component/settings/apps.vue:106 +#: src/component/settings/apps.vue:113 +#: src/component/settings/apps.vue:56 +#: src/component/settings/apps.vue:57 +#: src/component/settings/apps.vue:66 +#: src/component/settings/apps.vue:73 +#: src/component/settings/apps.vue:95 +#: src/component/settings/passcode.vue:110 +#: src/component/settings/passcode.vue:111 +#: src/component/settings/passcode.vue:120 +#: src/component/settings/passcode.vue:127 +#: src/component/settings/passcode.vue:165 +#: src/component/settings/passcode.vue:177 +#: src/component/settings/passcode.vue:183 +msgid "Copy" +msgstr "Kopēt" + +#: src/component/photo/batch-edit.vue:413 +#: src/component/photo/edit/details.vue:329 +msgid "Copyright" +msgstr "Autortiesības" + +#: src/component/photo/edit/people.vue:318 +msgid "Could not update person cover" +msgstr "Neizdevās atjaunināt personas profila attēlu" + +#: src/component/photo/view/list.vue:210 +msgid "Couldn't find anything." +msgstr "Neko nevarēja atrast." + +#: src/page/settings/account.vue:369 +#: src/component/photo/batch-edit.vue:333 +#: src/component/photo/edit/details.vue:172 +#: src/component/photo/toolbar.vue:111 +#: src/page/settings/account.vue:302 +msgid "Country" +msgstr "Valsts" + +#: src/page/auth/login.vue:130 +#: src/page/auth/login.vue:137 +msgid "Create Account" +msgstr "Izveidot kontu" + +#: src/component/photo/album/dialog.vue:102 +msgid "Create album" +msgstr "Izveidot albumu" + +#: src/page/settings/advanced.vue:174 +msgid "Create regular backups based on the configured schedule." +msgstr "Veidot regulāras rezerves kopijas saskaņā ar plānoto grafiku." + +#: src/page/settings/advanced.vue:190 +msgid "Create YAML files to back up album metadata." +msgstr "Izveidojiet YAML failus, lai dublētu albuma metadatus." + +#: src/page/settings/advanced.vue:206 +msgid "Create YAML sidecar files to back up picture metadata." +msgstr "Izveidojiet YAML blakusfailus, lai dublētu attēlu metadatus." + +#: src/page/admin/sessions.vue:191 +#: src/component/photo/edit/info.vue:206 +#: src/component/photo/edit/info.vue:207 +msgid "Created" +msgstr "Izveidots" + +#: src/page/library/index.vue:282 +msgid "Creating thumbnails for" +msgstr "Sīktēlu izveide priekš" + +#: src/options/options.js:488 +msgid "Cubic: Moderate Quality, Good Performance" +msgstr "Cubic: vidēja kvalitāte, laba veiktspēja" + +#: src/component/settings/password.vue:31 +msgid "Current Password" +msgstr "Esošā parole" + +#: src/options/themes.js:1002 +#: src/options/themes.js:1012 +msgid "Custom" +msgstr "Pielāgots" + +#: src/options/options.js:453 +msgid "Cyan" +msgstr "Gaiši zils" + +#: src/options/options.js:426 +msgid "Daily" +msgstr "Dienas" + +#: src/locales.js:327 +msgid "Database" +msgstr "Datubāze" + +#: src/page/settings/advanced.vue:173 +msgid "Database Backups" +msgstr "Datu bāzes rezerves kopijas" + +#: src/locales.js:328 +msgid "Databases" +msgstr "Datu bāzes" + +#: src/component/photo/batch-edit.vue:234 +msgid "Date & Time" +msgstr "Datums un laiks" + +#: src/page/settings/account.vue:220 +#: src/component/photo/batch-edit.vue:238 +#: src/component/photo/edit/details.vue:58 +#: src/page/settings/account.vue:220 +msgid "Day" +msgstr "Diena" + +#: src/component/settings/passcode.vue:232 +msgid "Deactivate" +msgstr "Deaktivizēt" + +#: src/page/settings/advanced.vue:36 +msgid "Debug Logs" +msgstr "Atkļūdošanas žurnāli" + +#: src/options/admin.js:35 +#: src/page/admin/sessions.vue:368 +#: src/page/admin/users.vue:371 +#: src/common/util.js:782 +#: src/model/session.js:42 +#: src/model/user.js:250 +#: src/options/auth.js:21 +#: src/options/auth.js:22 +#: src/options/auth.js:40 +#: src/options/auth.js:41 +#: src/options/options.js:252 +#: src/options/options.js:255 +#: src/options/options.js:293 +#: src/options/options.js:377 +#: src/options/themes.js:844 +msgid "Default" +msgstr "Noklusējuma" + +#: src/component/service/edit.vue:49 +msgid "Default Folder" +msgstr "Noklusējuma mape" + +#: src/component/session/remove/dialog.vue:22 +#: src/component/user/remove/dialog.vue:22 +#: src/component/album/clipboard.vue:76 +#: src/component/album/delete/dialog.vue:22 +#: src/component/album/toolbar.vue:73 +#: src/component/file/delete/dialog.vue:22 +#: src/component/label/clipboard.vue:42 +#: src/component/label/delete/dialog.vue:22 +#: src/component/photo/clipboard.vue:140 +#: src/component/photo/edit/files.vue:62 +#: src/component/photo/edit/files.vue:70 +#: src/component/photo/edit/files.vue:94 +#: src/component/service/remove.vue:22 +#: src/component/share/dialog.vue:112 +#: src/locales.js:317 +#: src/page/settings/general.vue:275 +msgid "Delete" +msgstr "Dzēst" + +#: src/component/album/toolbar.vue:213 +msgid "Delete Album" +msgstr "Dzēst albumu" + +#: src/component/photo/toolbar.vue:83 +#: src/page/library/errors.vue:47 +msgid "Delete All" +msgstr "Dzēst visu" + +#: src/component/photo/toolbar.vue:287 +#: src/page/library/errors.vue:109 +msgid "Delete all?" +msgstr "Dzēst visu?" + +#: src/page/library/index.vue:51 +msgid "Delete orphaned index entries, sidecar files and thumbnails." +msgstr "Dzēst nevajadzīgos indeksa ierakstus, blakusfailus un sīktēlus." + +#: src/options/options.js:13 +#: src/options/options.js:16 +msgid "deleted" +msgstr "izdzēsts" + +#: src/component/album/edit/dialog.vue:57 +#: src/component/photo/batch-edit.vue:199 +#: src/page/albums.vue:285 +msgid "Description" +msgstr "Apraksts" + +#: src/component/settings/webdav.vue:67 +msgid "Detailed instructions can be found in our User Guide." +msgstr "Detalizētus norādījumus var atrast mūsu lietotāja rokasgrāmatā." + +#: src/component/photo/edit/dialog.vue:42 +#: src/component/photo/edit/dialog.vue:45 +msgid "Details" +msgstr "Sīkāka informācija" + +#: src/component/photo/edit/files.vue:441 +msgid "Dimensions" +msgstr "Izmēri" + +#: src/page/settings/advanced.vue:117 +msgid "Disable all face detection and recognition features." +msgstr "Izslēgt visas sejas atpazīšanas un noteikšanas iespējas." + +#: src/page/settings/advanced.vue:84 +msgid "Disable Backups" +msgstr "Izslēgt rezerves kopijas" + +#: src/page/settings/advanced.vue:362 +msgid "Disable Darktable" +msgstr "Izslēgt Darktable" + +#: src/page/settings/advanced.vue:148 +msgid "Disable ExifTool" +msgstr "Izslēgt ExifTool" + +#: src/page/settings/advanced.vue:116 +msgid "Disable Faces" +msgstr "Izslēgt sejas" + +#: src/page/settings/advanced.vue:69 +msgid "Disable features that require write permission for the originals folder." +msgstr "Izslēgt funkcijas, kurām nepieciešama rakstīšanas atļauja oriģinālu mapē." + +#: src/page/settings/advanced.vue:426 +msgid "Disable FFmpeg" +msgstr "Izslēgt FFmpeg" + +#: src/page/settings/advanced.vue:410 +msgid "Disable ImageMagick" +msgstr "Izslēgt ImageMagick" + +#: src/page/settings/advanced.vue:133 +msgid "Disable interactive world maps and reverse geocoding." +msgstr "Izslēgt interaktīvo pasaules karti un adreses noteikšanu pēc koordinātēm." + +#: src/page/settings/advanced.vue:132 +msgid "Disable Places" +msgstr "Izslēgt vietas" + +#: src/page/settings/advanced.vue:378 +msgid "Disable RawTherapee" +msgstr "Izslēgt RawTherapee" + +#: src/page/settings/advanced.vue:442 +msgid "Disable Vectors" +msgstr "Izslēgt vektorus" + +#: src/page/settings/advanced.vue:100 +msgid "Disable WebDAV" +msgstr "Izslēgt WebDAV" + +#: src/component/photo/clipboard.vue:430 +msgid "Disabled" +msgstr "Izslēgt" + +#: src/page/settings/advanced.vue:443 +msgid "Disables vector graphics support." +msgstr "Izslēdz vektorgrafikas atbalstu." + +#: src/page/settings/advanced.vue:427 +msgid "Disables video transcoding and thumbnail extraction." +msgstr "Izslēdz video pārkodēšanu un sīktēlu izgūšanu." + +#: src/component/photo/batch-edit.vue:512 +#: src/component/photo/edit/details.vue:407 +msgid "Discard" +msgstr "Atcelt" + +#: src/component/photo/edit/details.vue:403 +msgid "Discard changes and close" +msgstr "Atcelt izmaiņas un aizvērt" + +#: src/app/routes.js:590 +#: src/app/routes.js:597 +#: src/app/routes.js:604 +#: src/app/routes.js:611 +msgid "Discover" +msgstr "Atklāt" + +#: src/locales.js:207 +msgid "Dismiss" +msgstr "Noraidīt" + +#: src/component/user/add/dialog.vue:67 +#: src/component/user/add/dialog.vue:68 +#: src/component/user/edit/dialog.vue:85 +#: src/component/user/edit/dialog.vue:86 +#: src/page/admin/users.vue:267 +#: src/page/settings/account.vue:76 +#: src/page/settings/account.vue:78 +#: src/locales.js:321 +#: src/page/settings/account.vue:76 +#: src/page/settings/account.vue:78 +msgid "Display Name" +msgstr "Parādāmais vārds" + +#: src/page/settings/content.vue:170 +msgid "Display picture captions in search results." +msgstr "Rādīt attēlu parakstus meklēšanas rezultātos." + +#: src/page/settings/content.vue:156 +msgid "Display picture titles in search results." +msgstr "Rādīt attēlu nosaukumus meklēšanas rezultātos." + +#: src/component/photo/view/cards.vue:149 +#: src/component/photo/view/cards.vue:298 +#: src/component/photo/view/mosaic.vue:96 +#: src/model/thumb.js:152 +#: src/options/options.js:370 +msgid "Document" +msgstr "Dokuments" + +#: src/component/navigation.vue:106 +#: src/component/navigation.vue:107 +#: src/component/navigation.vue:131 +#: src/component/navigation.vue:137 +#: src/component/navigation.vue:152 +#: src/component/navigation.vue:167 +#: src/component/navigation.vue:198 +#: src/component/navigation.vue:205 +msgid "Documents" +msgstr "Dokumenti" + +#: src/page/settings/advanced.vue:363 +msgid "Don't use Darktable to convert RAW images." +msgstr "Neizmantojiet Darktable, lai konvertētu RAW attēlus." + +#: src/page/settings/advanced.vue:411 +msgid "Don't use ImageMagick to convert images." +msgstr "Neizmantojiet ImageMagick attēlu konvertēšanai." + +#: src/page/settings/advanced.vue:379 +msgid "Don't use RawTherapee to convert RAW images." +msgstr "Neizmantojiet RawTherapee, lai konvertētu RAW attēlus." + +#: src/component/settings/apps.vue:103 +#: src/component/settings/apps.vue:53 +#: src/component/settings/apps.vue:54 +#: src/component/settings/apps.vue:63 +#: src/component/settings/apps.vue:70 +#: src/component/settings/apps.vue:92 +#: src/component/share/dialog.vue:177 +msgid "Done" +msgstr "Pabeigts" + +#: src/component/upload/dialog.vue:37 +#: src/page/library/import.vue:7 +#: src/page/library/index.vue:8 +msgid "Done." +msgstr "Pabeigts." + +#: src/component/album/clipboard.vue:54 +#: src/component/album/toolbar.vue:203 +#: src/component/file/clipboard.vue:32 +#: src/component/lightbox.vue:1479 +#: src/component/people/clipboard.vue:31 +#: src/component/photo/clipboard.vue:92 +#: src/component/photo/edit/files.vue:51 +#: src/page/settings/content.vue:183 +#: src/page/settings/general.vue:170 +msgid "Download" +msgstr "Lejupielādēt" + +#: src/page/settings/content.vue:194 +msgid "Download only original media files, without any automatically generated files." +msgstr "Lejupielādējiet tikai oriģinālos multimediju failus, neizmantojot automātiski ģenerētus failus." + +#: src/component/service/edit.vue:118 +msgid "Download remote files" +msgstr "Lejupielādēt attālinātos failus" + +#: src/component/album/clipboard.vue:227 +#: src/component/album/toolbar.vue:252 +#: src/component/file/clipboard.vue:150 +#: src/component/label/clipboard.vue:172 +#: src/component/lightbox.vue:2426 +#: src/component/people/clipboard.vue:144 +#: src/component/photo/clipboard.vue:421 +#: src/component/photo/edit/files.vue:527 +#: src/component/photo/view/cards.vue:509 +#: src/component/photo/view/list.vue:300 +#: src/page/library/browse.vue:252 +msgid "Downloading…" +msgstr "Notiek lejupielāde…" + +#: src/page/settings/advanced.vue:228 +msgid "Downscaling Filter" +msgstr "Samazināšanas filtrs" + +#: src/locales.js:276 +msgid "Drag and drop files here" +msgstr "Velciet un nometiet failus šeit" + +#: src/locales.js:344 +msgid "Driver" +msgstr "Draiveris" + +#: src/page/about/feedback.vue:22 +#: src/page/about/feedback.vue:23 +msgid "Due to the high volume of emails we receive, our team may be unable to get back to you immediately." +msgstr "Lielā e-pasta ziņojumu skaita dēļ, ko saņemam, mūsu komanda, iespējams, nevarēs ar jums nekavējoties atbildēt." + +#: src/page/library/browse.vue:46 +msgid "Duplicates will be skipped and only appear once." +msgstr "Dublikāti tiks izlaisti un parādīsies tikai vienu reizi." + +#: src/component/photo/edit/files.vue:163 +#: src/component/photo/edit/files.vue:164 +#: src/component/photo/edit/files.vue:170 +msgid "Duration" +msgstr "Ilgums" + +#: src/page/settings/advanced.vue:281 +msgid "Dynamic Previews" +msgstr "Dinamiskie priekšskatījumi" + +#: src/page/settings/advanced.vue:257 +msgid "Dynamic Size Limit: %{n}px" +msgstr "Dinamiskā izmēra ierobežojums: %{n}px" + +#: src/page/about/feedback.vue:80 +#: src/page/about/feedback.vue:79 +msgid "E-Mail" +msgstr "E-pasts" + +#: src/component/album/clipboard.vue:43 +#: src/component/album/toolbar.vue:174 +#: src/component/lightbox.vue:1378 +#: src/component/lightbox.vue:1379 +#: src/component/photo/clipboard.vue:68 +#: src/page/settings/general.vue:200 +msgid "Edit" +msgstr "Rediģēt" + +#: src/component/album/edit/dialog.vue:24 +#: src/component/label/edit/dialog.vue:25 +#: src/component/people/edit/dialog.vue:25 +msgid "Edit %{s}" +msgstr "Rediģēt %{s}" + +#: src/component/user/edit/dialog.vue:23 +#: src/component/service/edit.vue:28 +msgid "Edit Account" +msgstr "Rediģēt kontu" + +#: src/component/photo/edit/dialog.vue:169 +msgid "Edit Photo" +msgstr "Rediģēt fotoattēlu" + +#: src/component/photo/batch-edit.vue:664 +msgid "Edit Photos (%{n})" +msgstr "Rediģēt fotoattēlus (%{n})" + +#: src/component/photo/edit/info.vue:222 +#: src/component/photo/edit/info.vue:223 +msgid "Edited" +msgstr "Rediģēts" + +#: src/component/user/add/dialog.vue:81 +#: src/component/user/edit/dialog.vue:119 +#: src/page/settings/account.vue:320 +#: src/page/settings/account.vue:91 +#: src/page/settings/account.vue:91 +msgid "Email" +msgstr "E-pasts" + +#: src/page/settings/advanced.vue:37 +msgid "Enable debug mode to display additional logs and help with troubleshooting." +msgstr "Ieslēgt atkļūdošanas režīmu, lai parādītu papildu žurnālfailus un palīdzētu problēmu novēršanā." + +#: src/page/settings/general.vue:171 +msgid "Enable downloading of original and sidecar files from the web interface." +msgstr "Ieslēgt lietotāja saskarnē oriģinālo un blakusfailu lejupielādi." + +#: src/page/settings/general.vue:82 +msgid "Enable face recognition and the People view to easily find people you know." +msgstr "Ieslēgt sejas atpazīšanu un personas skatu, lai viegli atrastu cilvēkus, kurus pazīstat." + +#: src/page/settings/advanced.vue:53 +msgid "Enable new features that may be incomplete or unstable." +msgstr "Ieslēgt funkcionalitāti, kas varētu būt vēl nepilnīga vai nestabila." + +#: src/page/settings/general.vue:305 +msgid "Enable the file browser to navigate the Originals folder structure." +msgstr "Ieslēgt failu pārlūku, lai pārvietotos pa oriģinālu mapi." + +#: src/page/settings/advanced.vue:395 +msgid "Enables RAW converter presets. May reduce performance." +msgstr "Ieslēdz RAW pārveidotāja sākotnējos iestatījumus. Var samazināt veiktspēju." + +#: src/component/settings/passcode.vue:59 +msgid "Enabling two-factor authentication means that you will need a randomly generated verification code to log in, so even if someone gains access to your password, they will not be able to access your account." +msgstr "Divfaktoru autentifikācijas ieslēgšana nozīmē to, ka, lai pieteiktos, būs nepieciešams nejauši ģenerēts verifikācijas kods, tāpēc pat ja kāds iegūs piekļuvi jūsu parolei, viņš nevarēs piekļūt jūsu kontam." + +#: src/locales.js:345 +msgid "Engine" +msgstr "Dzinējs" + +#: src/locales.js:248 +#: src/locales.js:250 +msgid "Enter date" +msgstr "Ievadiet datumu" + +#: src/locales.js:245 +msgid "Enter dates" +msgstr "Ievadiet datumus" + +#: src/component/input/chip-selector.vue:129 +msgid "Enter item name..." +msgstr "Ievadiet nosaukumu..." + +#: src/component/settings/passcode.vue:101 +#: src/page/auth/login.vue:38 +msgid "Enter the code generated by your authenticator app:" +msgstr "Ievadiet autentifikācijas lietotnes ģenerēto kodu:" + +#: src/common/api.js:122 +msgid "Enter verification code" +msgstr "Ievadiet verifikācijas kodu" + +#: src/component/settings/apps.vue:32 +msgid "Enter your password to confirm the action and continue:" +msgstr "Ievadiet paroli, lai apstiprinātu darbību un turpinātu:" + +#: src/page/connect.vue:14 +#: src/component/lightbox.vue:415 +#: src/page/connect.vue:19 +#: src/page/library/errors.vue:393 +msgid "Error" +msgstr "Kļūda" + +#: src/app/routes.js:406 +#: src/component/navigation.vue:372 +#: src/component/navigation.vue:373 +#: src/component/navigation.vue:397 +#: src/component/navigation.vue:403 +#: src/component/navigation.vue:418 +#: src/component/navigation.vue:637 +#: src/component/navigation.vue:643 +#: src/component/navigation.vue:649 +msgid "Errors" +msgstr "Kļūdas" + +#: src/common/util.js:784 +msgid "Estimate" +msgstr "Noteikt" + +#: src/page/settings/content.vue:42 +msgid "Estimate Locations" +msgstr "Aptuvenās atrašanās vietas" + +#: src/page/settings/content.vue:43 +msgid "Estimate the approximate location of pictures without GPS coordinates." +msgstr "Noteikt aptuveno attēlu atrašanās vietu bez GPS koordinātām." + +#: src/options/options.js:427 +msgid "Every two days" +msgstr "Ik pēc divām dienām" + +#: src/page/people/new.vue:20 +#: src/page/people/recognized.vue:53 +msgid "Exclude hidden" +msgstr "Izlaist slēptos" + +#: src/page/settings/advanced.vue:149 +msgid "ExifTool is required for full support of XMP metadata, videos and Live Photos." +msgstr "Pilnīgam XMP metadatu, videoklipu un Live Photos atbalstam ir nepieciešams ExifTool." + +#: src/page/settings/advanced.vue:52 +msgid "Experimental Features" +msgstr "Eksperimentālās funkcijas" + +#: src/page/admin/sessions.vue:203 +#: src/page/admin/sessions.vue:296 +#: src/component/service/edit.vue:69 +#: src/component/settings/apps.vue:160 +#: src/component/settings/apps.vue:327 +#: src/component/share/dialog.vue:210 +msgid "Expires" +msgstr "Derīguma termiņš beidzas" + +#: src/component/photo/edit/details.vue:247 +msgid "Exposure" +msgstr "Ekspozīcija" + +#: src/page/settings/content.vue:59 +msgid "Extract still images and generate thumbnails while indexing." +msgstr "Izgūt statiskos attēlus un ģenerēt sīktēlus indeksēšanas laikā." + +#: src/component/photo/edit/details.vue:281 +msgid "F Number" +msgstr "F skaitlis" + +#: src/model/face.js:139 +msgid "Face" +msgstr "Seja" + +#: src/component/photo/edit/info.vue:111 +#: src/component/photo/edit/info.vue:112 +msgid "Faces" +msgstr "Sejas" + +#: src/component/share/dialog.vue:250 +msgid "Failed removing link" +msgstr "Neizdevās noņemt saiti" + +#: src/page/connect.vue:42 +#: src/page/connect.vue:44 +msgid "Failed to connect account." +msgstr "Neizdevās savienot kontu." + +#: src/component/photo/batch-edit.vue:1435 +msgid "Failed to save changes" +msgstr "Neizdevās saglabāt izmaiņas" + +#: src/component/share/dialog.vue:231 +msgid "Failed updating link" +msgstr "Neizdevās atjaunināt saiti" + +#: src/component/user/edit/dialog.vue:69 +#: src/component/user/edit/dialog.vue:70 +#: src/page/settings/account.vue:62 +#: src/page/settings/account.vue:64 +#: src/page/settings/account.vue:62 +#: src/page/settings/account.vue:64 +msgid "Family Name" +msgstr "Uzvārds" + +#: src/options/options.js:277 +msgid "Fast" +msgstr "Ātri" + +#: src/component/album/edit/dialog.vue:91 +#: src/component/label/edit/dialog.vue:44 +#: src/component/people/edit/dialog.vue:44 +#: src/component/photo/batch-edit.vue:1490 +#: src/component/photo/edit/info.vue:131 +#: src/component/photo/edit/info.vue:132 +msgid "Favorite" +msgstr "Favorīts" + +#: src/app/routes.js:251 +#: src/component/navigation.vue:412 +#: src/options/options.js:261 +#: src/page/albums.vue:459 +msgid "Favorites" +msgstr "Favorīti" + +#: src/page/about/feedback.vue:111 +#: src/options/options.js:463 +msgid "Feature Request" +msgstr "Funkcionalitātes pieprasījums" + +#: src/component/navigation.vue:406 +#: src/component/navigation.vue:407 +#: src/component/navigation.vue:431 +#: src/component/navigation.vue:437 +#: src/component/navigation.vue:452 +#: src/component/navigation.vue:683 +#: src/component/navigation.vue:691 +#: src/component/navigation.vue:699 +#: src/component/navigation.vue:707 +msgid "Feedback" +msgstr "Atsauksmes" + +#: src/options/options.js:494 +msgid "Female" +msgstr "Sieviete" + +#: src/common/util.js:786 +#: src/component/photo/edit/info.vue:104 +#: src/model/file.js:266 +msgid "File" +msgstr "Fails" + +#: src/app/routes.js:375 +msgid "File Browser" +msgstr "Failu pārlūks" + +#: src/page/settings/advanced.vue:350 +msgid "File Conversion" +msgstr "Failu konvertēšana" + +#: src/component/album/edit/dialog.vue:147 +#: src/component/photo/batch-edit.vue:635 +#: src/component/photo/batch-edit.vue:655 +#: src/component/photo/toolbar.vue:415 +#: src/component/photo/toolbar.vue:426 +#: src/component/photo/toolbar.vue:437 +#: src/component/photo/view/list.vue:47 +msgid "File Name" +msgstr "Faila nosaukums" + +#: src/component/album/edit/dialog.vue:148 +#: src/component/photo/toolbar.vue:416 +#: src/component/photo/toolbar.vue:427 +#: src/component/photo/toolbar.vue:438 +msgid "File Size" +msgstr "Faila izmērs" + +#: src/component/photo/edit/files.vue:100 +#: src/component/photo/edit/files.vue:94 +msgid "Filename" +msgstr "Faila nosaukums" + +#: src/component/navigation.vue:936 +#: src/component/photo/edit/dialog.vue:55 +#: src/component/photo/edit/dialog.vue:56 +#: src/component/photo/edit/dialog.vue:68 +#: src/component/photo/edit/dialog.vue:70 +#: src/component/photo/edit/dialog.vue:71 +msgid "Files" +msgstr "Faili" + +#: src/page/settings/content.vue:115 +msgid "Files with sequential names like 'IMG_1234 (2)' and 'IMG_1234 (3)' belong to the same picture." +msgstr "Faili ar secīgiem nosaukumiem, piemēram, “IMG_1234 (2)” un “IMG_1234 (3)”, pieder vienam attēlam." + +#: src/locales.js:234 +#: src/locales.js:292 +msgid "First page" +msgstr "Pirmā lapa" + +#: src/component/photo/edit/details.vue:295 +msgid "Focal Length" +msgstr "Fokusa attālums" + +#: src/component/photo/edit/info.vue:32 +#: src/component/photo/edit/info.vue:33 +#: src/component/service/edit.vue:44 +#: src/component/service/edit.vue:86 +#: src/component/service/edit.vue:91 +#: src/component/service/upload.vue:42 +#: src/component/service/upload.vue:48 +#: src/model/folder.js:175 +#: src/page/library/browse.vue:102 +msgid "Folder" +msgstr "Mape" + +#: src/page/library/browse.vue:486 +msgid "Folder contains %{n} files" +msgstr "Mapē ir %{n} faili" + +#: src/page/library/browse.vue:477 +msgid "Folder is empty" +msgstr "Mape ir tukša" + +#: src/app/routes.js:231 +#: src/component/navigation.vue:587 +#: src/options/options.js:266 +msgid "Folders" +msgstr "Mapes" + +#: src/page/auth/login.vue:169 +msgid "Forgot password?" +msgstr "Aizmirsu paroli?" + +#: src/component/photo/edit/files.vue:175 +#: src/component/photo/edit/files.vue:176 +#: src/component/photo/edit/files.vue:182 +msgid "FPS" +msgstr "FPS" + +#: src/component/photo/edit/files.vue:169 +#: src/component/photo/edit/files.vue:170 +#: src/component/photo/edit/files.vue:176 +msgid "Frames" +msgstr "Rāmji" + +#: src/options/auth.js:55 +#: src/options/auth.js:65 +msgid "Full Access" +msgstr "Pilna piekļuve" + +#: src/component/lightbox.vue:1324 +#: src/component/lightbox.vue:1325 +msgid "Fullscreen" +msgstr "Pilnekrāna režīms" + +#: src/page/settings.vue:60 +msgid "General" +msgstr "Vispārīgi" + +#: src/component/settings/apps.vue:104 +#: src/component/settings/apps.vue:144 +#: src/component/settings/apps.vue:176 +#: src/component/settings/apps.vue:87 +#: src/component/settings/apps.vue:88 +#: src/component/settings/apps.vue:97 +msgid "Generate" +msgstr "Ģenerēt" + +#: src/page/settings/content.vue:58 +msgid "Generate Previews" +msgstr "Ģenerēt priekšskatījumus" + +#: src/component/photo/edit/info.vue:90 +msgid "Generated" +msgstr "Ģenerēts" + +#: src/component/photo/toolbar.vue:485 +msgid "Get Started" +msgstr "Sākt darbu" + +#: src/component/user/edit/dialog.vue:53 +#: src/component/user/edit/dialog.vue:54 +#: src/page/settings/account.vue:47 +#: src/page/settings/account.vue:49 +#: src/page/settings/account.vue:47 +#: src/page/settings/account.vue:49 +msgid "Given Name" +msgstr "Vārds" + +#: src/page/settings/advanced.vue:24 +msgid "Global Options" +msgstr "Globālās opcijas" + +#: src/locales.js:290 +msgid "Go to page {0}" +msgstr "Dodieties uz {0} lapu" + +#: src/options/options.js:448 +msgid "Gold" +msgstr "Zelts" + +#: src/options/options.js:451 +msgid "Green" +msgstr "Zaļš" + +#: src/options/options.js:457 +msgid "Grey" +msgstr "Pelēks" + +#: src/options/admin.js:26 +#: src/options/auth.js:11 +msgid "Guest" +msgstr "Viesis" + +#: src/component/photo/edit/files.vue:84 +#: src/component/photo/edit/files.vue:90 +msgid "Hash" +msgstr "Hašs" + +#: src/page/help.vue:10 +msgid "Help" +msgstr "Palīdzība" + +#: src/app/routes.js:76 +#: src/app/routes.js:82 +msgid "Help & Support" +msgstr "Palīdzība un atbalsts" + +#: src/component/navigation.vue:365 +#: src/component/navigation.vue:366 +#: src/component/navigation.vue:390 +#: src/component/navigation.vue:396 +#: src/component/navigation.vue:411 +#: src/component/navigation.vue:630 +#: src/component/navigation.vue:636 +#: src/component/navigation.vue:642 +#: src/component/people/edit/dialog.vue:54 +msgid "Hidden" +msgstr "Slēpts" + +#: src/app/routes.js:390 +msgid "Hidden Files" +msgstr "Slēptie faili" + +#: src/page/people/new.vue:71 +#: src/page/people/recognized.vue:128 +msgid "Hide" +msgstr "Slēpt" + +#: src/page/settings/general.vue:141 +msgid "Hide private content from global views while keeping it accessible in the Private section." +msgstr "Slēpt privātu saturu no globāliem skatiem, vienlaikus saglabājot to pieejamu sadaļā Privāts." + +#: src/options/options.js:381 +msgid "High" +msgstr "Augsts" + +#: src/component/photo/edit/files.vue:189 +#: src/component/photo/edit/files.vue:190 +#: src/component/photo/edit/files.vue:196 +msgid "High Dynamic Range (HDR)" +msgstr "Augsts dinamiskais diapazons (HDR)" + +#: src/page/about/feedback.vue:52 +#: src/page/about/feedback.vue:53 +msgid "How can we help?" +msgstr "Kā mēs varam palīdzēt?" + +#: src/options/options.js:304 +msgid "Hybrid" +msgstr "Hibrīds" + +#: src/component/settings/passcode.vue:196 +msgid "If you lose access to your authenticator app or device, you can use your recovery code to regain access to your account." +msgstr "Ja zaudējat piekļuvi autentifikatora lietotnei vai ierīcei, varat izmantot atkopšanas kodu, lai atgūtu piekļuvi savam kontam." + +#: src/common/util.js:790 +#: src/component/photo/view/cards.vue:307 +#: src/model/file.js:194 +#: src/options/options.js:342 +msgid "Image" +msgstr "Attēls" + +#: src/page/settings/advanced.vue:297 +msgid "Image Quality" +msgstr "Attēla kvalitāte" + +#: src/page/library.vue:73 +#: src/page/library/import.vue:44 +#: src/page/library/import.vue:45 +#: src/page/library/import.vue:73 +#: src/page/library/import.vue:86 +#: src/page/settings/general.vue:185 +msgid "Import" +msgstr "Importēt" + +#: src/page/library/import.vue:57 +msgid "Imported files will be sorted by date and given a unique name to avoid duplicates." +msgstr "Importētie faili tiks sakārtoti pēc datuma un tiem tiks piešķirts unikāls nosaukums, lai izvairītos no dublikātiem." + +#: src/page/library/import.vue:5 +msgid "Importing %{s}…" +msgstr "Notiek %{s} importēšana…" + +#: src/page/library/import.vue:6 +msgid "Importing files to originals…" +msgstr "Failu importēšana uz oriģināliem…" + +#: src/component/photo/edit/files.vue:280 +#: src/component/photo/edit/files.vue:281 +#: src/component/photo/edit/files.vue:287 +#: src/component/photo/edit/files.vue:290 +#: src/component/photo/edit/files.vue:291 +#: src/component/photo/edit/files.vue:297 +msgid "in" +msgstr "iekšā" + +#: src/component/photo/view/cards.vue:20 +#: src/component/photo/view/list.vue:20 +#: src/component/photo/view/mosaic.vue:20 +#: src/page/labels.vue:75 +#: src/page/library/browse.vue:48 +msgid "In case pictures you expect are missing, please rescan your library and wait until indexing has been completed." +msgstr "Ja trūkst gaidīto attēlu, lūdzu, atkārtoti skenējiet savu bibliotēku un pagaidiet, līdz indeksēšana ir pabeigta." + +#: src/page/settings/content.vue:208 +msgid "Include RAW image files when downloading stacks and archives." +msgstr "Lejupielādējot kaudzes un arhīvus, iekļaujiet RAW attēlu failus." + +#: src/page/settings/content.vue:222 +msgid "Include sidecar files when downloading stacks and archives." +msgstr "Lejupielādējot stekus un arhīvus, iekļaujiet blakusfailus." + +#: src/component/upload/dialog.vue:38 +#: src/page/library/import.vue:9 +msgid "Increase storage size or delete files to continue." +msgstr "Lai turpinātu, palieliniet krātuves lielumu vai izdzēsiet failus." + +#: src/component/navigation.vue:942 +#: src/page/library.vue:63 +#: src/page/settings/content.vue:12 +msgid "Index" +msgstr "Indekss" + +#: src/page/library/index.vue:245 +#: src/page/library/index.vue:251 +msgid "Indexing" +msgstr "Indeksēšana" + +#: src/page/library/index.vue:7 +msgid "Indexing media and sidecar files…" +msgstr "Notiek multivides un blakusfailu indeksēšana…" + +#: src/component/lightbox.vue:1272 +#: src/component/lightbox.vue:1273 +#: src/component/sidebar/info.vue:9 +msgid "Information" +msgstr "Informācija" + +#: src/locales.js:360 +msgid "Instance" +msgstr "Instance" + +#: src/component/photo/edit/files.vue:82 +msgid "Instance ID" +msgstr "Instances ID" + +#: src/locales.js:361 +msgid "Instances" +msgstr "Instances" + +#: src/component/upload/dialog.vue:38 +#: src/page/library/import.vue:8 +msgid "Insufficient storage." +msgstr "Brīvas vietas vairs nav." + +#: src/component/service/edit.vue:100 +msgid "Interval" +msgstr "Intervāls" + +#: src/common/form.js:236 +#: src/common/form.js:238 +#: src/common/form.js:245 +#: src/common/form.js:247 +#: src/common/form.js:319 +#: src/common/form.js:322 +#: src/common/form.js:333 +#: src/common/form.js:336 +#: src/common/form.js:367 +#: src/common/form.js:370 +#: src/common/form.js:379 +#: src/common/form.js:382 +#: src/common/form.js:399 +#: src/common/form.js:402 +msgid "Invalid" +msgstr "Nederīgs" + +#: src/common/form.js:265 +#: src/common/form.js:268 +msgid "Invalid address" +msgstr "Nederīga adrese" + +#: src/common/form.js:351 +#: src/common/form.js:352 +#: src/common/form.js:356 +#: src/common/form.js:357 +msgid "Invalid country" +msgstr "Nederīga valsts" + +#: src/component/photo/edit/details.vue:644 +msgid "Invalid date" +msgstr "Nederīgs datums" + +#: src/page/connect.vue:294 +#: src/page/connect.vue:301 +#: src/page/connect.vue:302 +#: src/page/connect.vue:309 +msgid "Invalid parameters" +msgstr "Nederīgi parametri" + +#: src/component/photo/edit/dialog.vue:285 +#: src/model/thumb.js:175 +msgid "Invalid photo selected" +msgstr "Atlasīts nederīgs fotoattēls" + +#: src/common/form.js:254 +#: src/common/form.js:256 +msgid "Invalid time" +msgstr "Nederīgs laiks" + +#: src/common/form.js:275 +#: src/common/form.js:277 +msgid "Invalid URL" +msgstr "Nederīgs URL" + +#: src/page/admin/activity.vue:126 +#: src/locales.js:350 +msgid "IP Address" +msgstr "IP adrese" + +#: src/component/settings/passcode.vue:165 +#: src/component/settings/passcode.vue:200 +msgid "It is a one-time use code that will disable 2FA for your account when you use it." +msgstr "Tas ir vienreiz lietojams kods, kas atspējos 2FA jūsu kontam, kad to izmantosiet." + +#: src/model/rest.js:157 +msgid "Item" +msgstr "Ieraksts" + +#: src/locales.js:230 +msgid "Items per page:" +msgstr "Vienumi lapā:" + +#: src/page/settings/advanced.vue:304 +msgid "JPEG Quality: %{n}" +msgstr "JPEG kvalitāte: %{n}" + +#: src/page/settings/advanced.vue:319 +msgid "JPEG Size Limit: %{n}px" +msgstr "JPEG izmēra ierobežojums: %{n} pikseļi" + +#: src/page/library/import.vue:58 +msgid "JPEGs and thumbnails are automatically rendered as needed." +msgstr "JPEG faili un to sīktēli tiek automātiski radīti, kad nepieciešams." + +#: src/common/util.js:802 +msgid "Keyword" +msgstr "Atslēgvārds" + +#: src/component/photo/edit/details.vue:373 +msgid "Keywords" +msgstr "Atslēgvārdi" + +#: src/component/photo/edit/labels.vue:209 +#: src/component/photo/edit/labels.vue:39 +#: src/model/label.js:102 +msgid "Label" +msgstr "Birka" + +#: src/app/routes.js:421 +#: src/component/navigation.vue:560 +#: src/component/photo/batch-edit.vue:458 +#: src/component/photo/edit/dialog.vue:37 +#: src/component/photo/edit/dialog.vue:38 +#: src/component/photo/edit/dialog.vue:50 +#: src/component/photo/edit/dialog.vue:52 +#: src/component/photo/edit/dialog.vue:53 +#: src/locales.js:325 +#: src/options/options.js:265 +#: src/page/settings/general.vue:126 +msgid "Labels" +msgstr "Birkas" + +#: src/component/label/clipboard.vue:154 +msgid "Labels deleted" +msgstr "Birkas ir dzēstas" + +#: src/options/options.js:487 +msgid "Lanczos: Detail Preservation, Minimal Artifacts" +msgstr "Lanczos: Detaļu saglabāšana, minimāli nevēlami efekti" + +#: src/page/settings/general.vue:32 +msgid "Language" +msgstr "Valoda" + +#: src/page/admin/sessions.vue:197 +#: src/page/admin/sessions.vue:295 +msgid "Last Active" +msgstr "Pēdējais aktīvais" + +#: src/page/admin/users.vue:276 +#: src/locales.js:335 +msgid "Last Login" +msgstr "Pēdējā pieteikšanās" + +#: src/locales.js:235 +#: src/locales.js:293 +msgid "Last page" +msgstr "Pēdējā lapa" + +#: src/page/settings/services.vue:187 +msgid "Last Sync" +msgstr "Pēdējā sinhronizācija" + +#: src/component/settings/apps.vue:318 +msgid "Last Used" +msgstr "Pēdējo reizi izmantots" + +#: src/component/photo/edit/info.vue:176 +#: src/component/photo/edit/info.vue:177 +msgid "Latitude" +msgstr "Platums" + +#: src/options/admin.js:47 +#: src/options/auth.js:30 +msgid "LDAP/AD" +msgstr "LDAP/AD" + +#: src/page/connect.vue:30 +#: src/component/confirm/sponsor.vue:35 +#: src/page/about/about.vue:19 +#: src/page/about/about.vue:69 +#: src/page/connect.vue:32 +#: src/page/help.vue:19 +msgid "Learn more" +msgstr "Uzziniet vairāk" + +#: src/page/admin/sessions.vue:342 +#: src/page/admin/users.vue:330 +#: src/page/labels.vue:361 +msgid "Learn More" +msgstr "Uzziniet vairāk" + +#: src/component/navigation.vue:970 +msgid "Legal Information" +msgstr "Juridiskā informācija" + +#: src/component/photo/edit/details.vue:260 +#: src/component/photo/view/cards.vue:262 +msgid "Lens" +msgstr "Objektīvs" + +#: src/app/routes.js:475 +#: src/app/routes.js:491 +#: src/app/routes.js:507 +#: src/component/navigation.vue:613 +#: src/page/settings/general.vue:289 +msgid "Library" +msgstr "Bibliotēka" + +#: src/page/connect.vue:5 +#: src/app/routes.js:70 +#: src/component/navigation.vue:412 +#: src/component/navigation.vue:413 +#: src/component/navigation.vue:437 +#: src/component/navigation.vue:443 +#: src/component/navigation.vue:458 +#: src/component/navigation.vue:689 +#: src/component/navigation.vue:697 +#: src/component/navigation.vue:713 +#: src/component/navigation.vue:720 +#: src/component/photo/batch-edit.vue:444 +#: src/component/photo/edit/details.vue:358 +#: src/page/about/license.vue:10 +msgid "License" +msgstr "Licence" + +#: src/component/lightbox.vue:1342 +#: src/component/lightbox.vue:1343 +msgid "Like" +msgstr "Patīk" + +#: src/options/options.js:450 +msgid "Lime" +msgstr "Gaiši zaļš" + +#: src/page/library/browse.vue:490 +msgid "Limit reached, showing first %{n} files" +msgstr "Sasniegts limits, tiek rādīti pirmie %{n} faili" + +#: src/options/options.js:489 +msgid "Linear: Very Smooth, Best Performance" +msgstr "Lineārs: ļoti vienmērīgs, vislabākā veiktspēja" + +#: src/model/link.js:93 +#: src/options/auth.js:31 +#: src/options/auth.js:32 +msgid "Link" +msgstr "Saite" + +#: src/component/photo/toolbar.vue:397 +msgid "List" +msgstr "Saraksts" + +#: src/page/settings/content.vue:141 +msgid "List View" +msgstr "Saraksta skats" + +#: src/component/photo/batch-edit.vue:158 +#: src/component/photo/batch-edit.vue:76 +#: src/component/photo/view/cards.vue:139 +#: src/component/photo/view/cards.vue:280 +#: src/component/photo/view/list.vue:94 +#: src/component/photo/view/mosaic.vue:86 +#: src/options/options.js:350 +msgid "Live" +msgstr "Tiešraide" + +#: src/app/routes.js:265 +#: src/component/navigation.vue:191 +#: src/component/navigation.vue:192 +#: src/component/navigation.vue:216 +#: src/component/navigation.vue:222 +#: src/component/navigation.vue:237 +#: src/component/navigation.vue:331 +msgid "Live Photos" +msgstr "Tiešraides fotoattēli" + +#: src/locales.js:307 +msgid "Load more" +msgstr "Ielādēt vairāk" + +#: src/locales.js:214 +msgid "Loading items..." +msgstr "Notiek vienumu ielāde..." + +#: src/locales.js:305 +msgid "Loading..." +msgstr "Notiek ielāde..." + +#: src/options/admin.js:39 +#: src/locales.js:341 +#: src/options/auth.js:23 +#: src/options/auth.js:28 +#: src/options/options.js:93 +#: src/page/settings/general.vue:46 +msgid "Local" +msgstr "Vietējais" + +#: src/component/photo/edit/details.vue:122 +msgid "Local Time" +msgstr "Vietējais laiks" + +#: src/page/settings/account.vue:359 +#: src/page/settings/account.vue:361 +#: src/common/util.js:792 +#: src/component/album/edit/dialog.vue:46 +#: src/component/photo/batch-edit.vue:307 +#: src/component/photo/batch-edit.vue:615 +#: src/component/photo/edit/details.vue:471 +#: src/component/photo/edit/details.vue:591 +#: src/component/photo/edit/details.vue:685 +#: src/component/photo/view/cards.vue:325 +#: src/component/photo/view/list.vue:56 +#: src/component/sidebar/info.vue:61 +#: src/page/albums.vue:461 +#: src/page/settings/account.vue:292 +#: src/page/settings/account.vue:294 +msgid "Location" +msgstr "Atrašanās vieta" + +#: src/page/library/errors.vue:100 +msgid "Log messages appear here whenever PhotoPrism comes across broken files, or there are other potential issues." +msgstr "Žurnāla ziņojumi tiek parādīti šeit ikreiz, kad PhotoPrism atrod bojātus failus vai pastāv citas potenciālas problēmas." + +#: src/component/navigation.vue:757 +#: src/component/navigation.vue:893 +msgid "Login" +msgstr "Pieteikšanās" + +#: src/component/navigation.vue:856 +msgid "Logout" +msgstr "Izrakstīties" + +#: src/component/navigation.vue:948 +#: src/component/navigation.vue:951 +#: src/page/library.vue:86 +#: src/page/settings/general.vue:319 +msgid "Logs" +msgstr "Žurnāli" + +#: src/component/photo/edit/info.vue:184 +#: src/component/photo/edit/info.vue:185 +msgid "Longitude" +msgstr "Garums" + +#: src/component/navigation.vue:799 +msgid "Lost server connection" +msgstr "Zaudēts savienojums ar serveri" + +#: src/options/options.js:385 +msgid "Low" +msgstr "Zems" + +#: src/options/options.js:444 +msgid "Magenta" +msgstr "Fuksīna" + +#: src/component/photo/edit/files.vue:252 +#: src/component/photo/edit/files.vue:253 +#: src/component/photo/edit/files.vue:259 +msgid "Main Color" +msgstr "Galvenā krāsa" + +#: src/options/options.js:493 +msgid "Male" +msgstr "Vīrietis" + +#: src/page/connect.vue:131 +#: src/page/connect.vue:71 +#: src/page/about/about.vue:101 +#: src/page/connect.vue:141 +#: src/page/connect.vue:73 +msgid "Manage Account" +msgstr "Pārvaldīt kontu" + +#: src/options/admin.js:10 +#: src/options/auth.js:7 +msgid "Manager" +msgstr "Vadītājs" + +#: src/common/util.js:814 +msgid "Manual" +msgstr "Manuāli" + +#: src/component/service/edit.vue:16 +msgid "Manual Upload" +msgstr "Manuāla augšupielāde" + +#: src/page/settings/general.vue:375 +msgid "Maps" +msgstr "Kartes" + +#: src/common/util.js:794 +#: src/model/marker.js:120 +msgid "Marker" +msgstr "Marķieris" + +#: src/component/user/add/dialog.vue:21 +msgid "Maximum number of accounts has been reached." +msgstr "Ir sasniegts maksimālais kontu skaits." + +#: src/app/routes.js:258 +#: src/component/navigation.vue:309 +#: src/options/options.js:258 +msgid "Media" +msgstr "Mediji" + +#: src/options/options.js:281 +msgid "Medium" +msgstr "Vidējs" + +#: src/page/connect.vue:10 +msgid "Membership" +msgstr "Dalība" + +#: src/component/people/merge/dialog.vue:57 +msgid "Merge %{a} with %{b}?" +msgstr "Apvienot %{a} ar %{b}?" + +#: src/page/about/feedback.vue:51 +#: src/page/admin/activity.vue:127 +#: src/locales.js:352 +#: src/page/about/feedback.vue:52 +msgid "Message" +msgstr "Ziņojums" + +#: src/page/about/feedback.vue:150 +#: src/page/about/feedback.vue:142 +msgid "Message sent" +msgstr "Ziņojums nosūtīts" + +#: src/common/util.js:804 +msgid "Metadata" +msgstr "Metadati" + +#: src/options/auth.js:57 +#: src/options/auth.js:73 +msgid "Metrics" +msgstr "Metrika" + +#: src/component/navigation.vue:82 +msgid "Minimize" +msgstr "Minimizēt" + +#: src/component/photo/edit/files.vue:268 +#: src/component/photo/edit/files.vue:269 +#: src/component/photo/edit/files.vue:275 +#: src/component/photo/edit/info.vue:233 +msgid "Missing" +msgstr "Trūkst" + +#: src/page/auth/login.vue:343 +msgid "Missing or invalid configuration" +msgstr "Trūkstoša vai nederīga konfigurācija" + +#: src/options/options.js:24 +#: src/options/options.js:27 +msgid "mixed" +msgstr "jaukts" + +#: src/app/routes.js:192 +#: src/component/navigation.vue:533 +#: src/options/options.js:264 +#: src/page/settings/general.vue:111 +msgid "Moments" +msgstr "Mirkļi" + +#: src/component/navigation.vue:101 +#: src/component/navigation.vue:107 +#: src/component/navigation.vue:122 +#: src/component/navigation.vue:137 +#: src/component/navigation.vue:143 +#: src/component/navigation.vue:76 +#: src/component/navigation.vue:77 +msgid "Monochrome" +msgstr "Vienkrāsains" + +#: src/page/settings/account.vue:240 +#: src/component/photo/batch-edit.vue:257 +#: src/component/photo/edit/details.vue:80 +#: src/component/photo/toolbar.vue:210 +#: src/page/settings/account.vue:240 +msgid "Month" +msgstr "Mēnesis" + +#: src/component/lightbox.vue:1397 +msgid "More options" +msgstr "Vairāk iespēju" + +#: src/component/photo/toolbar.vue:395 +#: src/component/photo/toolbar.vue:401 +msgid "Mosaic" +msgstr "Mozaīka" + +#: src/component/album/edit/dialog.vue:150 +#: src/component/photo/toolbar.vue:441 +msgid "Most Relevant" +msgstr "Visatbilstošākais" + +#: src/page/library/import.vue:44 +msgid "Move Files" +msgstr "Pārvietot failus" + +#: src/component/settings/password.vue:49 +msgid "Must have at least %{n} characters." +msgstr "Jābūt vismaz %{n} rakstzīmēm." + +#: src/component/lightbox.vue:1291 +#: src/component/lightbox.vue:1292 +msgid "Mute" +msgstr "Izslēgt skaņu" + +#: src/page/about/feedback.vue:65 +#: src/page/about/feedback.vue:66 +#: src/common/util.js:788 +#: src/component/album/edit/dialog.vue:36 +#: src/component/label/edit/dialog.vue:35 +#: src/component/people/edit/dialog.vue:35 +#: src/component/photo/edit/files.vue:435 +#: src/component/photo/edit/info.vue:42 +#: src/component/photo/edit/info.vue:43 +#: src/component/photo/edit/people.vue:163 +#: src/component/photo/view/list.vue:56 +#: src/component/service/edit.vue:159 +#: src/component/settings/apps.vue:134 +#: src/component/settings/apps.vue:307 +#: src/locales.js:319 +#: src/page/about/feedback.vue:65 +#: src/page/about/feedback.vue:66 +#: src/page/albums.vue:460 +#: src/page/auth/login.vue:87 +#: src/page/people/new.vue:213 +#: src/page/settings/services.vue:183 +msgid "Name" +msgstr "Vārds" + +#: src/component/album/edit/dialog.vue:154 +#: src/component/album/toolbar.vue:145 +#: src/component/label/edit/dialog.vue:83 +#: src/component/people/edit/dialog.vue:93 +#: src/component/photo/edit/labels.vue:233 +#: src/component/photo/edit/people.vue:166 +#: src/page/labels.vue:222 +#: src/page/library/browse.vue:159 +#: src/page/people/new.vue:186 +#: src/page/people/recognized.vue:247 +msgid "Name too long" +msgstr "Vārds ir pārāk garš" + +#: src/page/admin/users.vue:399 +#: src/options/options.js:422 +#: src/options/options.js:432 +#: src/page/settings/services.vue:221 +msgid "Never" +msgstr "Nekad" + +#: src/page/people.vue:73 +msgid "New" +msgstr "Jauns" + +#: src/component/settings/password.vue:48 +msgid "New Password" +msgstr "Jauna parole" + +#: src/component/album/edit/dialog.vue:143 +#: src/component/photo/toolbar.vue:410 +#: src/component/photo/toolbar.vue:422 +#: src/component/photo/toolbar.vue:432 +#: src/page/albums.vue:462 +msgid "Newest First" +msgstr "Jaunākie vispirms" + +#: src/component/lightbox.vue:414 +#: src/locales.js:297 +msgid "Next" +msgstr "Nākamais" + +#: src/locales.js:232 +#: src/locales.js:288 +msgid "Next page" +msgstr "Nākamā lapa" + +#: src/locales.js:256 +msgid "Next visual" +msgstr "Nākamais vizuālais materiāls" + +#: src/component/people/merge/dialog.vue:19 +#: src/component/photo/archive/dialog.vue:19 +#: src/component/photo/batch-edit.vue:1087 +#: src/component/photo/edit/info.vue:167 +#: src/component/photo/edit/info.vue:181 +#: src/component/photo/edit/info.vue:195 +#: src/component/photo/edit/info.vue:209 +#: src/component/photo/edit/info.vue:223 +msgid "No" +msgstr "Nē" + +#: src/component/photo/batch-edit.vue:480 +msgid "No albums assigned" +msgstr "Nav piešķirts neviens albums" + +#: src/page/albums.vue:1172 +#: src/page/albums.vue:163 +msgid "No albums found" +msgstr "Nav atrasts neviens albums" + +#: src/locales.js:253 +msgid "No data available" +msgstr "Dati nav pieejami" + +#: src/component/photo/batch-edit.vue:466 +msgid "No labels assigned" +msgstr "Nav piešķirtas etiķetes" + +#: src/page/labels.vue:70 +#: src/page/labels.vue:965 +msgid "No labels found" +msgstr "Nav atrastas etiķetes" + +#: src/locales.js:213 +msgid "No matching records found" +msgstr "Nav atrasti atbilstoši ieraksti" + +#: src/locales.js:308 +msgid "No more" +msgstr "Vairs ne" + +#: src/component/photo/edit/people.vue:12 +#: src/page/people/new.vue:43 +#: src/page/people/new.vue:491 +#: src/page/people/new.vue:597 +#: src/page/people/recognized.vue:739 +#: src/page/people/recognized.vue:83 +msgid "No people found" +msgstr "Nav atrastas personas" + +#: src/component/photo/view/cards.vue:14 +#: src/component/photo/view/list.vue:14 +#: src/component/photo/view/mosaic.vue:14 +#: src/page/album/photos.vue:570 +#: src/page/library/browse.vue:43 +#: src/page/photos.vue:708 +#: src/page/places.vue:781 +#: src/page/places.vue:902 +msgid "No pictures found" +msgstr "Nav atrasts neviens attēls" + +#: src/component/photo/view/cards.vue:11 +#: src/component/photo/view/list.vue:11 +#: src/component/photo/view/mosaic.vue:11 +msgid "No recently edited pictures" +msgstr "Nav nesen rediģētu attēlu" + +#: src/component/location/dialog.vue:89 +msgid "No results" +msgstr "Nav rezultātu" + +#: src/component/service/upload.vue:174 +msgid "No servers configured." +msgstr "Nav konfigurētu serveru." + +#: src/page/settings/services.vue:11 +msgid "No services configured." +msgstr "Nav konfigurētu pakalpojumu." + +#: src/component/confirm/sponsor.vue:32 +msgid "No thanks" +msgstr "Nē, paldies" + +#: src/page/library/errors.vue:96 +msgid "No warnings or error containing this keyword. Note that search is case-sensitive." +msgstr "Nav brīdinājumu vai kļūdu, kas satur šo atslēgvārdu. Ņemiet vērā, ka meklēšana ir reģistrjutīga." + +#: src/locales.js:356 +msgid "Node" +msgstr "Mezgls" + +#: src/locales.js:357 +msgid "Nodes" +msgstr "Mezgli" + +#: src/component/photo/view/cards.vue:26 +#: src/component/photo/view/list.vue:217 +#: src/component/photo/view/list.vue:26 +#: src/component/photo/view/mosaic.vue:26 +#: src/component/upload/dialog.vue:101 +msgid "Non-photographic and low-quality images require a review before they appear in search results." +msgstr "Attēli, kas nav fotoattēli un ir zemas kvalitātes, ir jāpārskata, pirms tie parādās meklēšanas rezultātos." + +#: src/options/admin.js:51 +#: src/options/auth.js:33 +#: src/options/options.js:273 +#: src/options/options.js:389 +msgid "None" +msgstr "Neviens" + +#: src/component/lightbox.vue:846 +#: src/component/service/upload.vue:159 +#: src/component/service/upload.vue:171 +msgid "Not allowed" +msgstr "Nav atļauts" + +#: src/component/lightbox.vue:843 +msgid "Not found" +msgstr "Nav atrasts" + +#: src/locales.js:222 +msgid "Not sorted." +msgstr "Nav sakārtots." + +#: src/component/lightbox.vue:840 +#: src/component/lightbox.vue:961 +msgid "Not supported" +msgstr "Nav atbalstīts" + +#: src/page/library/import.vue:60 +msgid "Note you may manually manage your originals folder and importing is optional." +msgstr "Ņemiet vērā, ka varat manuāli pārvaldīt savu oriģinālu mapi, un importēšana nav obligāta." + +#: src/page/settings/services.vue:78 +msgid "Note:" +msgstr "Piezīme:" + +#: src/component/service/add.vue:61 +msgid "Note: Only WebDAV servers, like Nextcloud or PhotoPrism, can be configured as remote service for backup and file upload." +msgstr "Piezīme. Kā attālinātus pakalpojumus dublēšanai un failu augšupielādei var konfigurēt tikai WebDAV serverus, piemēram, Nextcloud vai PhotoPrism." + +#: src/component/photo/edit/details.vue:388 +msgid "Notes" +msgstr "Piezīmes" + +#: src/page/library/logs.vue:5 +msgid "Nothing to see here yet." +msgstr "Šeit vēl nav ko redzēt." + +#: src/page/admin/activity.vue:60 +#: src/page/admin/sessions.vue:47 +#: src/page/admin/users.vue:56 +#: src/component/settings/apps.vue:198 +msgid "Nothing was found." +msgstr "Nekas netika atrasts." + +#: src/options/options.js:331 +msgid "Offline" +msgstr "Bezsaistē" + +#: src/options/admin.js:43 +#: src/options/auth.js:29 +msgid "OIDC" +msgstr "OIDC" + +#: src/locales.js:209 +msgid "OK" +msgstr "Labi" + +#: src/component/album/edit/dialog.vue:144 +#: src/component/photo/toolbar.vue:411 +#: src/component/photo/toolbar.vue:423 +#: src/component/photo/toolbar.vue:433 +#: src/page/albums.vue:463 +msgid "Oldest First" +msgstr "Vecākais vispirms" + +#: src/component/settings/webdav.vue:17 +#: src/component/settings/webdav.vue:18 +#: src/component/settings/webdav.vue:27 +#: src/component/settings/webdav.vue:38 +msgid "On Windows, enter the following resource in the connection dialog:" +msgstr "Operētājsistēmā Windows savienojuma dialoglodziņā ievadiet šādu resursu:" + +#: src/page/settings/advanced.vue:283 +msgid "On-demand generation of thumbnails may cause high CPU and memory usage. It is not recommended for resource-constrained servers and NAS devices." +msgstr "Sīktēlu ģenerēšana pēc pieprasījuma var izraisīt lielu centrālā procesora un atmiņas izmantošanu. Tas nav ieteicams serveriem un NAS ierīcēm ar ierobežotiem resursiem." + +#: src/options/options.js:428 +msgid "Once a week" +msgstr "Reizi nedēļā" + +#: src/page/albums.vue:1174 +msgid "One album found" +msgstr "Atrasts viens albums" + +#: src/page/library/browse.vue:479 +msgid "One file found" +msgstr "Atrasts viens fails" + +#: src/component/service/upload.vue:188 +msgid "One file uploaded" +msgstr "Augšupielādēts viens fails" + +#: src/page/library/browse.vue:481 +msgid "One folder found" +msgstr "Atrasta viena mape" + +#: src/page/labels.vue:967 +msgid "One label found" +msgstr "Atrasta viena etiķete" + +#: src/page/people/new.vue:493 +#: src/page/people/new.vue:599 +#: src/page/people/recognized.vue:741 +msgid "One person found" +msgstr "Atrasta viena persona" + +#: src/page/album/photos.vue:572 +#: src/page/photos.vue:710 +msgid "One picture found" +msgstr "Atrasta viena bilde" + +#: src/component/settings/passcode.vue:250 +msgid "Only locally managed accounts can be set up for authentication with 2FA." +msgstr "Divfaktoru autentifikāciju var iestatīt tikai lokāli pārvaldītiem kontiem." + +#: src/locales.js:205 +#: src/locales.js:318 +msgid "Open" +msgstr "Atvērt" + +#: src/component/service/add.vue:39 +#: src/component/service/add.vue:53 +msgid "optional" +msgstr "neobligāts" + +#: src/locales.js:277 +msgid "or" +msgstr "vai" + +#: src/options/options.js:447 +msgid "Orange" +msgstr "Oranžs" + +#: src/page/settings/account.vue:305 +#: src/page/settings/account.vue:307 +msgid "Organization" +msgstr "Organizācija" + +#: src/component/photo/edit/files.vue:219 +#: src/component/photo/edit/files.vue:220 +#: src/component/photo/edit/files.vue:226 +msgid "Orientation" +msgstr "Orientācija" + +#: src/page/library/import.vue:59 +msgid "Original file names will be stored and indexed." +msgstr "Sākotnējie failu nosaukumi tiks saglabāti un indeksēti." + +#: src/component/photo/edit/files.vue:109 +#: src/component/photo/edit/files.vue:110 +#: src/component/photo/edit/files.vue:116 +#: src/component/photo/edit/info.vue:52 +#: src/component/photo/edit/info.vue:53 +msgid "Original Name" +msgstr "Sākotnējais nosaukums" + +#: src/component/navigation.vue:356 +#: src/component/navigation.vue:357 +#: src/component/navigation.vue:381 +#: src/component/navigation.vue:387 +#: src/component/navigation.vue:402 +#: src/component/navigation.vue:621 +#: src/component/navigation.vue:627 +#: src/component/navigation.vue:633 +#: src/model/file.js:172 +#: src/options/options.js:474 +#: src/page/library/browse.vue:12 +#: src/page/settings/content.vue:193 +#: src/page/settings/general.vue:304 +msgid "Originals" +msgstr "Oriģināli" + +#: src/page/about/feedback.vue:113 +#: src/options/options.js:465 +#: src/options/options.js:495 +msgid "Other" +msgstr "Citi" + +#: src/page/about/about.vue:29 +#: src/page/about/about.vue:29 +msgid "Our mission is to provide the most user- and privacy-friendly solution to keep your pictures organized and accessible." +msgstr "Mūsu misija ir nodrošināt lietotājam un privātumam draudzīgāko risinājumu, lai jūsu attēli būtu sakārtoti un pieejami." + +#: src/options/options.js:316 +msgid "Outdoor" +msgstr "Ārā" + +#: src/locales.js:291 +msgid "Page {0}, Current page" +msgstr "{0}. lapa, pašreizējā lapa" + +#: src/component/photo/edit/files.vue:137 +#: src/component/photo/edit/files.vue:138 +#: src/component/photo/edit/files.vue:144 +#: src/model/photo.js:872 +msgid "Pages" +msgstr "Lapas" + +#: src/locales.js:287 +msgid "Pagination Navigation" +msgstr "Lappušu navigācija" + +#: src/component/photo/batch-edit.vue:1492 +#: src/component/photo/edit/info.vue:155 +#: src/component/photo/edit/info.vue:156 +msgid "Panorama" +msgstr "Panorāma" + +#: src/component/navigation.vue:107 +#: src/component/navigation.vue:113 +#: src/component/navigation.vue:128 +#: src/component/navigation.vue:143 +#: src/component/navigation.vue:149 +#: src/component/navigation.vue:155 +#: src/component/navigation.vue:82 +#: src/component/navigation.vue:83 +msgid "Panoramas" +msgstr "Panorāmas" + +#: src/component/input/chip-selector.vue:188 +msgid "Part of all selected photos" +msgstr "Daļa no visām atlasītajām fotogrāfijām" + +#: src/component/input/chip-selector.vue:186 +msgid "Part of some selected photos" +msgstr "Daļa no dažām atlasītajām fotogrāfijām" + +#: src/component/user/add/dialog.vue:53 +#: src/component/user/add/dialog.vue:54 +#: src/component/service/add.vue:52 +#: src/component/service/edit.vue:179 +#: src/component/settings/apps.vue:40 +#: src/component/settings/passcode.vue:214 +#: src/component/settings/passcode.vue:43 +#: src/component/share/dialog.vue:175 +#: src/options/auth.js:92 +#: src/page/auth/login.vue:108 +msgid "Password" +msgstr "Parole" + +#: src/component/settings/password.vue:166 +msgid "Password changed" +msgstr "Parole nomainīta" + +#: src/app/routes.js:436 +#: src/app/routes.js:460 +#: src/component/navigation.vue:385 +#: src/component/navigation.vue:918 +#: src/component/photo/edit/dialog.vue:46 +#: src/component/photo/edit/dialog.vue:47 +#: src/component/photo/edit/dialog.vue:59 +#: src/component/photo/edit/dialog.vue:61 +#: src/component/photo/edit/dialog.vue:62 +#: src/options/options.js:260 +#: src/page/settings/general.vue:81 +msgid "People" +msgstr "Cilvēki" + +#: src/component/share/dialog.vue:130 +msgid "People you share a link with will be able to view public contents." +msgstr "Personas, ar kurām kopīgojat saiti, varēs skatīt publisko saturu." + +#: src/component/photo/clipboard.vue:315 +#: src/component/photo/toolbar.vue:558 +msgid "Permanently deleted" +msgstr "Neatgriezeniski dzēsts" + +#: src/model/subject.js:134 +msgid "Person" +msgstr "Persona" + +#: src/component/photo/edit/people.vue:313 +msgid "Person cover updated" +msgstr "Personas vāks atjaunināts" + +#: src/component/photo/edit/people.vue:263 +#: src/component/photo/edit/people.vue:298 +msgid "Person not found" +msgstr "Persona nav atrasta" + +#: src/options/auth.js:43 +msgid "Personal" +msgstr "Personīgā" + +#: src/model/photo.js:1286 +msgid "Photo" +msgstr "Foto" + +#: src/page/about/about.vue:87 +msgid "PhotoPrism is 100% self-funded and independent." +msgstr "PhotoPrism ir 100% pašfinansēts un neatkarīgs." + +#: src/page/about/about.vue:44 +msgid "PhotoPrism+ Membership" +msgstr "PhotoPrism+ dalība" + +#: src/app/routes.js:185 +msgid "Photos" +msgstr "Fotogrāfijas" + +#: src/component/photo/batch-edit.vue:629 +#: src/component/photo/batch-edit.vue:649 +msgid "Picture" +msgstr "Attēls" + +#: src/component/album/edit/dialog.vue:146 +#: src/component/photo/toolbar.vue:414 +#: src/component/photo/toolbar.vue:425 +#: src/component/photo/toolbar.vue:436 +msgid "Picture Title" +msgstr "Attēla nosaukums" + +#: src/options/options.js:445 +msgid "Pink" +msgstr "Rozā" + +#: src/component/photo/edit/info.vue:163 +#: src/component/photo/edit/info.vue:164 +msgid "Place" +msgstr "Vieta" + +#: src/page/settings/content.vue:83 +msgid "Place & Time" +msgstr "Vieta un laiks" + +#: src/app/routes.js:335 +#: src/app/routes.js:341 +#: src/app/routes.js:347 +#: src/app/routes.js:362 +#: src/component/navigation.vue:462 +#: src/component/navigation.vue:927 +#: src/options/options.js:262 +#: src/page/places.vue:18 +#: src/page/settings/general.vue:349 +#: src/page/settings/general.vue:363 +msgid "Places" +msgstr "Vietas" + +#: src/component/settings/password.vue:68 +msgid "Please confirm your new password." +msgstr "Lūdzu, apstipriniet savu jauno paroli." + +#: src/component/settings/apps.vue:76 +msgid "Please copy the following randomly generated app password and keep it in a safe place, as you will not be able to see it again:" +msgstr "Lūdzu, nokopējiet šo nejauši ģenerēto lietotnes paroli un glabājiet to drošā vietā, jo jūs to vairs nevarēsiet redzēt:" + +#: src/component/upload/dialog.vue:94 +msgid "Please do not upload any private, unlawful or offensive pictures." +msgstr "Lūdzu, neaugšupielādējiet privātus, nelikumīgus vai aizskarošus attēlus." + +#: src/component/upload/dialog.vue:97 +msgid "Please don't upload photos containing offensive content." +msgstr "Lūdzu, neaugšupielādējiet fotoattēlus ar aizskarošu saturu." + +#: src/locales.js:269 +msgid "Please enter OTP character {0}" +msgstr "Lūdzu, ievadiet vienreizējās paroles rakstzīmi {0}" + +#: src/component/settings/password.vue:21 +msgid "Please note that changing your password will log you out on other devices and browsers." +msgstr "Lūdzu, ņemiet vērā, ka, mainot paroli, jūs tiksiet izrakstīts no citām ierīcēm un pārlūkprogrammām." + +#: src/page/connect.vue:65 +#: src/page/connect.vue:67 +msgid "Please restart your instance for the changes to take effect." +msgstr "Lūdzu, restartējiet instanci, lai izmaiņas stātos spēkā." + +#: src/common/notify.js:116 +msgid "Please wait…" +msgstr "Lūdzu, uzgaidiet…" + +#: src/locales.js:282 +msgid "PM" +msgstr "PM" + +#: src/page/settings/advanced.vue:334 +msgid "PNG Size Limit: %{n}px" +msgstr "PNG izmēra ierobežojums: %{n} pikseļi" + +#: src/locales.js:323 +msgid "Portal" +msgstr "Portāls" + +#: src/component/photo/edit/files.vue:197 +#: src/component/photo/edit/files.vue:198 +#: src/component/photo/edit/files.vue:204 +msgid "Portrait" +msgstr "Portrets" + +#: src/component/service/edit.vue:139 +msgid "Preserve filenames" +msgstr "Saglabāt failu nosaukumus" + +#: src/component/photo/album/dialog.vue:41 +#: src/component/upload/dialog.vue:65 +msgid "Press enter to create a new album." +msgstr "Lai izveidotu jaunu albumu, nospiediet taustiņu Enter." + +#: src/component/input/chip-selector.vue:61 +msgid "Press enter to create new item" +msgstr "Nospiediet taustiņu Enter, lai izveidotu jaunu vienumu" + +#: src/page/settings/advanced.vue:85 +msgid "Prevent database and album backups as well as YAML sidecar files from being created." +msgstr "Novērst datubāzes un albuma dublējumu, kā arī YAML blakusfailu izveidi." + +#: src/page/settings/advanced.vue:101 +msgid "Prevent other apps from accessing PhotoPrism as a shared network drive." +msgstr "Neļaujiet citām lietotnēm piekļūt PhotoPrism kā koplietojamam tīkla diskam." + +#: src/component/photo/edit/files.vue:30 +msgid "Preview" +msgstr "Priekšskatījums" + +#: src/page/settings/advanced.vue:218 +msgid "Preview Images" +msgstr "Priekšskatīt attēlus" + +#: src/component/lightbox.vue:413 +#: src/locales.js:298 +msgid "Previous" +msgstr "Iepriekšējais" + +#: src/locales.js:233 +#: src/locales.js:289 +msgid "Previous page" +msgstr "Iepriekšējā lapa" + +#: src/locales.js:255 +msgid "Previous visual" +msgstr "Iepriekšējais vizuālais materiāls" + +#: src/component/photo/edit/files.vue:181 +#: src/component/photo/edit/files.vue:182 +#: src/component/photo/edit/files.vue:188 +#: src/component/photo/edit/files.vue:428 +#: src/component/photo/edit/files.vue:52 +#: src/component/photo/edit/files.vue:60 +#: src/component/photo/edit/files.vue:63 +msgid "Primary" +msgstr "Primārā" + +#: src/app/routes.js:300 +#: src/component/album/edit/dialog.vue:100 +#: src/component/navigation.vue:120 +#: src/component/navigation.vue:121 +#: src/component/navigation.vue:145 +#: src/component/navigation.vue:151 +#: src/component/navigation.vue:166 +#: src/component/navigation.vue:181 +#: src/component/navigation.vue:226 +#: src/component/navigation.vue:233 +#: src/component/photo/batch-edit.vue:1491 +#: src/component/photo/edit/info.vue:139 +#: src/component/photo/edit/info.vue:140 +#: src/page/settings/general.vue:140 +msgid "Private" +msgstr "Privāts" + +#: src/page/about/feedback.vue:110 +#: src/options/options.js:462 +msgid "Product Feedback" +msgstr "Produkta atsauksmes" + +#: src/component/photo/edit/files.vue:205 +#: src/component/photo/edit/files.vue:206 +#: src/component/photo/edit/files.vue:212 +msgid "Projection" +msgstr "Projekcija" + +#: src/options/options.js:443 +msgid "Purple" +msgstr "Violets" + +#: src/page/settings/content.vue:23 +msgid "Quality Filter" +msgstr "Kvalitātes filtrs" + +#: src/component/photo/edit/info.vue:97 +#: src/component/photo/edit/info.vue:98 +msgid "Quality Score" +msgstr "Kvalitātes rādītājs" + +#: src/page/discover.vue:16 +#: src/page/discover.vue:17 +#: src/page/discover.vue:24 +msgid "Random" +msgstr "Nejauši" + +#: src/locales.js:302 +msgid "Rating {0} of {1}" +msgstr "Vērtējums {0} no {1}" + +#: src/options/options.js:346 +msgid "Raw" +msgstr "Neapstrādāts" + +#: src/component/photo/view/cards.vue:138 +#: src/component/photo/view/mosaic.vue:85 +#: src/page/settings/content.vue:207 +msgid "RAW" +msgstr "RAW" + +#: src/page/library/index.vue:40 +msgid "Re-index all originals, including already indexed and unchanged files." +msgstr "Atkārtoti indeksēt visus oriģinālus, tostarp jau indeksētos un nemainītos failus." + +#: src/page/settings/advanced.vue:68 +msgid "Read-Only Mode" +msgstr "Tikai lasīšanas režīms" + +#: src/component/album/edit/dialog.vue:145 +#: src/component/photo/toolbar.vue:412 +#: src/component/photo/toolbar.vue:424 +#: src/component/photo/toolbar.vue:434 +#: src/page/albums.vue:464 +msgid "Recently Added" +msgstr "Nesen pievienots" + +#: src/component/photo/toolbar.vue:413 +msgid "Recently Archived" +msgstr "Nesen arhivēti" + +#: src/component/photo/toolbar.vue:435 +#: src/page/albums.vue:465 +msgid "Recently Edited" +msgstr "Nesen rediģēts" + +#: src/component/photo/edit/people.vue:16 +#: src/page/people/new.vue:47 +#: src/page/people/recognized.vue:88 +msgid "Recognition starts after indexing has been completed." +msgstr "Atpazīšana sākas pēc indeksēšanas pabeigšanas." + +#: src/page/people.vue:60 +msgid "Recognized" +msgstr "Atzīt" + +#: src/page/auth/login.vue:47 +msgid "Recovery Code" +msgstr "Atkopšanas kods" + +#: src/options/options.js:446 +msgid "Red" +msgstr "Sarkans" + +#: src/page/admin/activity.vue:42 +#: src/page/admin/sessions.vue:333 +#: src/page/admin/users.vue:321 +#: src/component/album/toolbar.vue:164 +#: src/component/photo/toolbar.vue:465 +#: src/locales.js:316 +#: src/page/albums.vue:546 +#: src/page/labels.vue:296 +#: src/page/library/browse.vue:21 +#: src/page/library/errors.vue:228 +#: src/page/people/new.vue:7 +#: src/page/people/recognized.vue:36 +msgid "Refresh" +msgstr "Atsvaidzināt" + +#: src/app/routes.js:369 +#: src/component/navigation.vue:275 +#: src/component/navigation.vue:276 +#: src/component/navigation.vue:300 +#: src/component/navigation.vue:306 +#: src/component/navigation.vue:321 +#: src/component/navigation.vue:440 +#: src/component/navigation.vue:469 +#: src/component/navigation.vue:477 +msgid "Regions" +msgstr "Reģioni" + +#: src/page/connect.vue:131 +#: src/page/connect.vue:138 +#: src/page/connect.vue:155 +#: src/page/connect.vue:88 +#: src/page/connect.vue:89 +#: src/page/connect.vue:96 +#: src/page/connect.vue:107 +#: src/page/connect.vue:142 +#: src/page/connect.vue:148 +#: src/page/connect.vue:165 +#: src/page/connect.vue:94 +#: src/page/connect.vue:95 +msgid "Register" +msgstr "Reģistrēties" + +#: src/component/navigation.vue:862 +#: src/component/update.vue:28 +msgid "Reload" +msgstr "Pārlādēt" + +#: src/component/navigation.vue:1089 +#: src/component/update.vue:62 +#: src/page/settings/general.vue:511 +msgid "Reloading…" +msgstr "Atkārtota ielāde…" + +#: src/component/service/edit.vue:22 +msgid "Remote Sync" +msgstr "Attālā sinhronizācija" + +#: src/component/photo/edit/people.vue:31 +msgid "Remove" +msgstr "Noņemt" + +#: src/component/photo/clipboard.vue:367 +msgid "remove failed: unknown album" +msgstr "Neizdevās noņemt: nezināms albums" + +#: src/component/lightbox.vue:1434 +#: src/component/photo/clipboard.vue:128 +msgid "Remove from Album" +msgstr "Noņemt no albuma" + +#: src/component/input/chip-selector.vue:184 +msgid "Remove from all" +msgstr "Noņemt no visiem" + +#: src/component/input/chip-selector.vue:184 +msgid "Remove from all selected photos" +msgstr "Noņemt no visiem atlasītajiem fotoattēliem" + +#: src/page/library/import.vue:46 +msgid "Remove imported files to save storage. Unsupported file types will never be deleted, they remain in their current location." +msgstr "Noņemiet importētos failus, lai ietaupītu vietu krātuvē. Neatbalstīti failu tipi nekad netiks dzēsti, tie paliks to pašreizējā atrašanās vietā." + +#: src/locales.js:362 +msgid "Remove the selected instance from the cluster registry?" +msgstr "Vai noņemt atlasīto instanci no klastera reģistra?" + +#: src/locales.js:326 +msgid "Removed" +msgstr "Noņemts" + +#: src/page/admin/activity.vue:129 +#: src/locales.js:353 +msgid "Repeated" +msgstr "Atkārtots" + +#: src/common/api.js:102 +#: src/component/lightbox.vue:957 +msgid "Request failed - are you offline?" +msgstr "Pieprasījums neizdevās. Vai esat bezsaistē?" + +#: src/common/api.js:74 +msgid "Request failed - invalid response" +msgstr "Pieprasījums neizdevās — nederīga atbilde" + +#: src/page/settings/content.vue:25 +msgid "Require non-photographic and low-quality images to be reviewed before they appear in search results." +msgstr "Pieprasīt, lai attēli, kas nav fotoattēli un ir zemas kvalitātes, tiktu pārskatīti, pirms tie parādās meklēšanas rezultātos." + +#: src/component/photo/edit/info.vue:105 +#: src/component/photo/edit/info.vue:106 +msgid "Resolution" +msgstr "Izšķirtspēja" + +#: src/page/connect.vue:74 +#: src/page/connect.vue:83 +#: src/page/connect.vue:76 +#: src/page/connect.vue:85 +#: src/page/settings/advanced.vue:456 +msgid "Restart" +msgstr "Restartēt" + +#: src/component/user/edit/dialog.vue:106 +#: src/component/user/edit/dialog.vue:107 +#: src/component/user/edit/dialog.vue:116 +#: src/component/user/edit/dialog.vue:122 +#: src/component/user/edit/dialog.vue:315 +#: src/component/lightbox.vue:1464 +#: src/component/photo/clipboard.vue:56 +msgid "Restore" +msgstr "Atjaunot" + +#: src/component/lightbox.vue:2405 +msgid "Restored" +msgstr "Atjaunots" + +#: src/component/service/edit.vue:228 +msgid "Retry Limit" +msgstr "Atkārtotu mēģinājumu limits" + +#: src/component/settings/password.vue:67 +msgid "Retype Password" +msgstr "Atkārtojiet paroli" + +#: src/app/routes.js:293 +#: src/component/navigation.vue:113 +#: src/component/navigation.vue:114 +#: src/component/navigation.vue:138 +#: src/component/navigation.vue:144 +#: src/component/navigation.vue:159 +#: src/component/navigation.vue:174 +#: src/component/navigation.vue:212 +#: src/component/navigation.vue:219 +msgid "Review" +msgstr "Atsauksme" + +#: src/component/user/add/dialog.vue:94 +#: src/component/user/edit/dialog.vue:167 +#: src/page/admin/sessions.vue:145 +#: src/page/admin/users.vue:268 +#: src/locales.js:336 +msgid "Role" +msgstr "Loma" + +#: src/locales.js:337 +msgid "Roles" +msgstr "Lomas" + +#: src/locales.js:346 +msgid "Rotated" +msgstr "Pagriezts" + +#: src/locales.js:217 +msgid "Rows per page:" +msgstr "Rindas lapā:" + +#: src/options/options.js:310 +msgid "Satellite" +msgstr "Satelīts" + +#: src/component/user/edit/dialog.vue:106 +#: src/component/user/edit/dialog.vue:107 +#: src/component/user/edit/dialog.vue:116 +#: src/component/user/edit/dialog.vue:122 +#: src/component/user/edit/dialog.vue:315 +#: src/component/album/edit/dialog.vue:112 +#: src/component/album/edit/dialog.vue:41 +#: src/component/album/edit/dialog.vue:42 +#: src/component/album/edit/dialog.vue:52 +#: src/component/album/edit/dialog.vue:58 +#: src/component/label/edit/dialog.vue:24 +#: src/component/label/edit/dialog.vue:25 +#: src/component/label/edit/dialog.vue:36 +#: src/component/label/edit/dialog.vue:43 +#: src/component/label/edit/dialog.vue:57 +#: src/component/people/edit/dialog.vue:28 +#: src/component/people/edit/dialog.vue:29 +#: src/component/people/edit/dialog.vue:40 +#: src/component/people/edit/dialog.vue:47 +#: src/component/people/edit/dialog.vue:67 +#: src/component/photo/batch-edit.vue:523 +#: src/component/photo/edit/details.vue:419 +#: src/component/service/edit.vue:103 +#: src/component/service/edit.vue:246 +#: src/component/service/edit.vue:93 +#: src/component/service/edit.vue:94 +#: src/component/settings/password.vue:31 +#: src/component/settings/password.vue:32 +#: src/component/settings/password.vue:41 +#: src/component/settings/password.vue:86 +#: src/component/share/dialog.vue:110 +#: src/component/share/dialog.vue:118 +#: src/component/share/dialog.vue:72 +#: src/component/share/dialog.vue:73 +msgid "Save" +msgstr "Saglabāt" + +#: src/component/photo/edit/details.vue:414 +msgid "Save changes" +msgstr "Saglabāt izmaiņas" + +#: src/component/photo/batch-edit.vue:1489 +#: src/component/photo/edit/info.vue:147 +#: src/component/photo/edit/info.vue:148 +msgid "Scan" +msgstr "Skenēt" + +#: src/component/settings/passcode.vue:87 +msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated verification code:" +msgstr "Noskenējiet QR kodu ar autentifikācijas lietotni vai izmantojiet tālāk norādīto iestatīšanas atslēgu un pēc tam ievadiet ģenerēto verifikācijas kodu:" + +#: src/component/navigation.vue:100 +#: src/component/navigation.vue:101 +#: src/component/navigation.vue:125 +#: src/component/navigation.vue:131 +#: src/component/navigation.vue:146 +#: src/component/navigation.vue:161 +#: src/component/navigation.vue:186 +#: src/component/navigation.vue:192 +msgid "Scans" +msgstr "Skenējumi" + +#: src/page/admin/sessions.vue:179 +#: src/page/admin/sessions.vue:293 +#: src/component/settings/apps.vue:151 +#: src/component/settings/apps.vue:309 +#: src/locales.js:339 +msgid "Scope" +msgstr "Darbības joma" + +#: src/locales.js:340 +msgid "Scopes" +msgstr "Darbības jomas" + +#: src/page/admin/activity.vue:13 +#: src/page/admin/sessions.vue:13 +#: src/page/admin/users.vue:13 +#: src/component/location/dialog.vue:64 +#: src/component/navigation.vue:122 +#: src/component/navigation.vue:903 +#: src/component/photo/toolbar.vue:34 +#: src/locales.js:315 +#: src/options/options.js:256 +#: src/page/albums.vue:30 +#: src/page/labels.vue:25 +#: src/page/library/errors.vue:28 +#: src/page/people/recognized.vue:14 +#: src/page/places.vue:33 +#: src/page/settings/content.vue:131 +msgid "Search" +msgstr "Meklēt" + +#: src/page/discover.vue:12 +#: src/page/discover.vue:13 +#: src/page/discover.vue:20 +msgid "Season" +msgstr "Sezona" + +#: src/component/share/dialog.vue:88 +msgid "Secret" +msgstr "Noslēpums" + +#: src/page/settings/account.vue:149 +#: src/page/settings/account.vue:149 +msgid "Security and Access" +msgstr "Drošība un piekļuve" + +#: src/component/lightbox.vue:1360 +#: src/component/lightbox.vue:1361 +msgid "Select" +msgstr "Atlasīt" + +#: src/page/library/import.vue:12 +msgid "Select a source folder to import files…" +msgstr "Atlasiet avota mapi, lai importētu failus…" + +#: src/locales.js:247 +msgid "Select date" +msgstr "Izvēlieties datumu" + +#: src/locales.js:244 +msgid "Select dates" +msgstr "Izvēlieties datumus" + +#: src/component/photo/album/dialog.vue:34 +#: src/component/photo/batch-edit.vue:479 +#: src/component/upload/dialog.vue:58 +msgid "Select or create albums" +msgstr "Atlasīt vai izveidot albumus" + +#: src/component/photo/batch-edit.vue:465 +msgid "Select or create labels" +msgstr "Atlasiet vai izveidojiet etiķetes" + +#: src/component/upload/dialog.vue:41 +msgid "Select the files to upload…" +msgstr "Atlasiet augšupielādējamos failus…" + +#: src/page/library/index.vue:9 +msgid "Select the folder to be indexed…" +msgstr "Atlasiet mapi, kas jāindeksē…" + +#: src/locales.js:283 +msgid "Select Time" +msgstr "Izvēlieties laiku" + +#: src/component/photo/batch-edit.vue:113 +msgid "Selection" +msgstr "Atlase" + +#: src/component/photo/clipboard.vue:265 +msgid "Selection approved" +msgstr "Atlase apstiprināta" + +#: src/component/photo/clipboard.vue:295 +msgid "Selection archived" +msgstr "Atlase arhivēta" + +#: src/component/photo/clipboard.vue:328 +msgid "Selection restored" +msgstr "Atlase atjaunota" + +#: src/page/settings/content.vue:113 +msgid "Sequential Name" +msgstr "Secīgs nosaukums" + +#: src/locales.js:358 +msgid "Service" +msgstr "Pakalpojums" + +#: src/component/service/add.vue:27 +#: src/component/service/edit.vue:167 +#: src/component/share/dialog.vue:173 +msgid "Service URL" +msgstr "Pakalpojuma URL" + +#: src/locales.js:359 +#: src/page/settings.vue:99 +#: src/page/settings/general.vue:245 +msgid "Services" +msgstr "Pakalpojumi" + +#: src/locales.js:342 +#: src/model/session.js:83 +#: src/options/auth.js:42 +#: src/options/auth.js:91 +msgid "Session" +msgstr "Sesija" + +#: src/page/admin/sessions.vue:290 +msgid "Session ID" +msgstr "Sesijas ID" + +#: src/page/admin.vue:65 +#: src/locales.js:343 +msgid "Sessions" +msgstr "Sesijas" + +#: src/component/lightbox.vue:1419 +msgid "Set as Album Cover" +msgstr "Iestatīt kā albuma vāku" + +#: src/component/photo/edit/people.vue:240 +msgid "Set as Cover Image" +msgstr "Iestatīt kā vāka attēlu" + +#: src/app/routes.js:102 +#: src/app/routes.js:524 +#: src/app/routes.js:536 +#: src/app/routes.js:553 +#: src/app/routes.js:566 +#: src/app/routes.js:579 +#: src/component/navigation.vue:671 +#: src/component/navigation.vue:876 +#: src/options/options.js:267 +msgid "Settings" +msgstr "Iestatījumi" + +#: src/page/settings/account.vue:533 +#: src/page/settings/account.vue:562 +#: src/component/settings/passcode.vue:415 +#: src/page/settings/account.vue:466 +#: src/page/settings/account.vue:495 +msgid "Settings saved" +msgstr "Iestatījumi saglabāti" + +#: src/component/service/upload.vue:25 +#: src/component/service/upload.vue:26 +#: src/component/service/upload.vue:35 +#: src/component/service/upload.vue:60 +#: src/component/settings/passcode.vue:55 +#: src/component/settings/passcode.vue:71 +msgid "Setup" +msgstr "Iestatīšana" + +#: src/page/admin/activity.vue:125 +#: src/page/admin/activity.vue:36 +#: src/locales.js:347 +msgid "Severity" +msgstr "Smagums" + +#: src/component/album/clipboard.vue:32 +#: src/component/album/toolbar.vue:183 +#: src/component/photo/clipboard.vue:32 +#: src/page/settings/general.vue:230 +msgid "Share" +msgstr "Kopīgot" + +#: src/component/share/dialog.vue:14 +msgid "Share %{s}" +msgstr "Kopīgot %{s}" + +#: src/page/people/new.vue:70 +#: src/page/people/recognized.vue:127 +msgid "Show" +msgstr "Rādīt" + +#: src/page/labels.vue:153 +#: src/page/labels.vue:306 +msgid "Show All Labels" +msgstr "Rādīt visas etiķetes" + +#: src/page/people/new.vue:118 +#: src/page/people/new.vue:52 +msgid "Show all new faces" +msgstr "Rādīt visas jaunās sejas" + +#: src/page/settings/content.vue:169 +msgid "Show Captions" +msgstr "Rādīt parakstus" + +#: src/page/people/new.vue:12 +#: src/page/people/recognized.vue:45 +msgid "Show hidden" +msgstr "Rādīt paslēptos" + +#: src/page/labels.vue:315 +msgid "Show Important Only" +msgstr "Rādīt tikai svarīgos" + +#: src/page/settings/general.vue:320 +msgid "Show logs in the web interface to monitor activity and troubleshoot problems." +msgstr "Rādīt žurnālus tīmekļa saskarnē, lai uzraudzītu aktivitātes un novērstu problēmas." + +#: src/page/settings/general.vue:112 +msgid "Show smart albums that group pictures by occasion, trip, or location." +msgstr "Rādīt viedos albumus, kas grupē attēlus pēc notikuma, ceļojuma vai atrašanās vietas." + +#: src/page/settings/general.vue:335 +msgid "Show the Account page so users can manage their profile and security settings." +msgstr "Rādīt konta lapu, lai lietotāji varētu pārvaldīt savu profilu un drošības iestatījumus." + +#: src/page/settings/general.vue:97 +msgid "Show the Calendar view to browse the library by year and month." +msgstr "Rādīt kalendāra skatu, lai pārlūkotu bibliotēku pēc gada un mēneša." + +#: src/page/settings/general.vue:127 +msgid "Show the Labels section to view and manage AI-generated labels." +msgstr "Rādīt sadaļu Etiķetes, lai skatītu un pārvaldītu mākslīgā intelekta ģenerētās etiķetes." + +#: src/page/settings/general.vue:290 +msgid "Show the Library section to index, manage, and monitor the media library." +msgstr "Rādīt sadaļu Bibliotēka, lai indeksētu, pārvaldītu un pārraudzītu multivides bibliotēku." + +#: src/page/settings/general.vue:350 +msgid "Show the Places view with interactive maps so you can browse photos by location." +msgstr "Rādīt vietu skatu ar interaktīvām kartēm, lai varētu pārlūkot fotoattēlus pēc atrašanās vietas." + +#: src/page/settings/content.vue:155 +msgid "Show Titles" +msgstr "Rādīt nosaukumus" + +#: src/model/file.js:190 +#: src/page/settings/content.vue:221 +msgid "Sidecar" +msgstr "Blakusvāģis" + +#: src/page/settings/advanced.vue:205 +msgid "Sidecar Files" +msgstr "Blakusvāģu faili" + +#: src/page/auth/login.vue:133 +#: src/page/auth/login.vue:147 +#: src/page/auth/login.vue:42 +#: src/page/auth/login.vue:49 +#: src/page/auth/login.vue:63 +msgid "Sign in" +msgstr "Pierakstīties" + +#: src/page/discover.vue:16 +#: src/page/discover.vue:8 +#: src/page/discover.vue:9 +msgid "Similar" +msgstr "Līdzīgi" + +#: src/locales.js:351 +msgid "Site URL" +msgstr "Vietnes URL" + +#: src/component/photo/edit/files.vue:127 +#: src/component/photo/edit/files.vue:128 +#: src/component/photo/edit/files.vue:134 +#: src/component/photo/edit/files.vue:449 +#: src/component/service/edit.vue:58 +#: src/component/sidebar/info.vue:51 +msgid "Size" +msgstr "Izmērs" + +#: src/component/lightbox.vue:1307 +#: src/component/lightbox.vue:1308 +msgid "Slideshow" +msgstr "Slaidrāde" + +#: src/options/options.js:285 +msgid "Slow" +msgstr "Lēns" + +#: src/component/photo/edit/files.vue:143 +#: src/component/photo/edit/files.vue:144 +#: src/component/photo/edit/files.vue:150 +msgid "Software" +msgstr "Programmatūra" + +#: src/component/update.vue:15 +msgid "Software Update" +msgstr "Programmatūras atjauninājums" + +#: src/component/album/clipboard.vue:206 +msgid "Some albums could not be copied" +msgstr "Dažus albumus nevarēja nokopēt" + +#: src/component/photo/album/dialog.vue:194 +msgid "Some albums could not be created. Please edit the names and try again." +msgstr "Dažus albumus nevarēja izveidot. Lūdzu, rediģējiet nosaukumus un mēģiniet vēlreiz." + +#: src/component/file/clipboard.vue:136 +#: src/component/label/clipboard.vue:138 +#: src/component/people/clipboard.vue:132 +#: src/component/photo/clipboard.vue:356 +msgid "Some albums could not be updated" +msgstr "Dažus albumus nevarēja atjaunināt" + +#: src/component/lightbox.vue:954 +msgid "Something went wrong, try again" +msgstr "Kaut kas nogāja greizi, mēģiniet vēlreiz" + +#: src/locales.js:227 +msgid "Sort by" +msgstr "Kārtot pēc" + +#: src/page/labels.vue:333 +msgid "Sort by Name (A–Z)" +msgstr "Kārtot pēc nosaukuma (A–Z)" + +#: src/page/labels.vue:342 +msgid "Sort by Photo Count" +msgstr "Kārtot pēc fotoattēlu skaita" + +#: src/page/labels.vue:324 +msgid "Sort by Relevance" +msgstr "Kārtot pēc atbilstības" + +#: src/component/album/edit/dialog.vue:78 +#: src/component/photo/toolbar.vue:172 +#: src/page/albums.vue:112 +msgid "Sort Order" +msgstr "Kārtošanas secība" + +#: src/locales.js:221 +msgid "Sorted ascending." +msgstr "Kārtots augošā secībā." + +#: src/locales.js:220 +msgid "Sorted descending." +msgstr "Kārtots dilstošā secībā." + +#: src/component/photo/edit/labels.vue:215 +#: src/component/photo/edit/labels.vue:48 +msgid "Source" +msgstr "Avots" + +#: src/component/photo/view/cards.vue:154 +#: src/component/photo/view/mosaic.vue:101 +msgid "Stack" +msgstr "Kaudze" + +#: src/page/settings/content.vue:99 +msgid "Stack files sharing the same unique image or instance identifier." +msgstr "Sakraut failus, kuriem ir viens un tas pats unikālais attēls vai instances identifikators." + +#: src/page/settings/content.vue:84 +msgid "Stack pictures taken at the exact same time and location based on their metadata." +msgstr "Apvienojiet attēlus, kas uzņemti vienā un tajā pašā laikā un vietā, pamatojoties uz to metadatiem." + +#: src/component/photo/edit/info.vue:123 +#: src/component/photo/edit/info.vue:124 +msgid "Stackable" +msgstr "Sakraujams" + +#: src/component/navigation.vue:113 +#: src/component/navigation.vue:119 +#: src/component/navigation.vue:134 +#: src/component/navigation.vue:149 +#: src/component/navigation.vue:161 +#: src/component/navigation.vue:167 +#: src/component/navigation.vue:88 +#: src/component/navigation.vue:89 +#: src/page/settings/content.vue:72 +msgid "Stacks" +msgstr "Skursteņi" + +#: src/page/library/index.vue:64 +#: src/page/library/index.vue:70 +msgid "Start" +msgstr "Sākt" + +#: src/page/settings/general.vue:61 +msgid "Start Page" +msgstr "Sākumlapa" + +#: src/page/settings/advanced.vue:240 +msgid "Static Size Limit: %{n}px" +msgstr "Statiskā izmēra ierobežojums: %{n}px" + +#: src/component/photo/edit/files.vue:463 +msgid "Status" +msgstr "Statuss" + +#: src/component/photo/edit/files.vue:103 +#: src/component/photo/edit/files.vue:104 +#: src/component/photo/edit/files.vue:110 +msgid "Storage" +msgstr "Uzglabāšana" + +#: src/options/options.js:298 +msgid "Streets" +msgstr "Ielas" + +#: src/common/util.js:806 +#: src/component/photo/batch-edit.vue:378 +#: src/component/photo/edit/details.vue:314 +msgid "Subject" +msgstr "Tēma" + +#: src/page/about/feedback.vue:92 +#: src/page/about/feedback.vue:92 +msgid "Submit" +msgstr "Iesniegt" + +#: src/component/settings/passcode.vue:400 +msgid "Successfully activated" +msgstr "Veiksmīgi aktivizēts" + +#: src/page/connect.vue:11 +#: src/page/connect.vue:16 +msgid "Successfully Connected" +msgstr "Veiksmīgi izveidots savienojums" + +#: src/component/session/remove/dialog.vue:60 +#: src/component/settings/apps.vue:476 +msgid "Successfully deleted" +msgstr "Veiksmīgi dzēsts" + +#: src/component/settings/passcode.vue:372 +msgid "Successfully verified" +msgstr "Veiksmīgi verificēts" + +#: src/component/user/edit/dialog.vue:271 +#: src/page/admin/users.vue:358 +msgid "Super Admin" +msgstr "Superadministrators" + +#: src/component/service/add.vue:65 +msgid "Support for additional services, like Google Drive, will be added over time." +msgstr "Atbalsts papildu pakalpojumiem, piemēram, Google diskam, tiks pievienots laika gaitā." + +#: src/component/confirm/sponsor.vue:15 +#: src/component/navigation.vue:787 +msgid "Support Our Mission" +msgstr "Atbalstiet mūsu misiju" + +#: src/page/settings/services.vue:185 +msgid "Sync" +msgstr "Sinhronizācija" + +#: src/component/service/edit.vue:148 +msgid "Sync raw and video files" +msgstr "Sinhronizēt neapstrādātus un video failus" + +#: src/component/photo/edit/info.vue:101 +#: src/component/photo/view/cards.vue:248 +#: src/component/photo/view/cards.vue:60 +#: src/component/photo/view/list.vue:50 +#: src/component/sidebar/info.vue:40 +msgid "Taken" +msgstr "Paņemts" + +#: src/options/options.js:452 +msgid "Teal" +msgstr "Zilganzaļa" + +#: src/options/options.js:322 +msgid "Terrain" +msgstr "Reljefs" + +#: src/common/form.js:284 +msgid "Text" +msgstr "Teksts" + +#: src/component/photo/edit/details.vue:473 +#: src/page/people/new.vue:216 +msgid "Text too long" +msgstr "Teksts ir pārāk garš" + +#: src/page/about/about.vue:84 +msgid "Thank You for Your Support!" +msgstr "Paldies par jūsu atbalstu!" + +#: src/page/about/about.vue:34 +#: src/page/about/about.vue:34 +msgid "That's why PhotoPrism was built from the ground up to run wherever you need it, without compromising freedom, privacy, or functionality." +msgstr "Tāpēc PhotoPrism tika izstrādāts no paša sākuma, lai darbotos jebkur, kur jums tas nepieciešams, neapdraudot brīvību, privātumu vai funkcionalitāti." + +#: src/page/library/index.vue:84 +msgid "The index currently contains %{n} hidden files." +msgstr "Indeksā pašlaik ir %{n} slēpti faili." + +#: src/page/library/index.vue:86 +msgid "Their format may not be supported, they haven't been converted to JPEG yet or there are duplicates." +msgstr "To formāts var netikt atbalstīts, tie vēl nav konvertēti uz JPEG vai arī ir dublikāti." + +#: src/locales.js:324 +#: src/page/settings/general.vue:18 +msgid "Theme" +msgstr "Tēma" + +#: src/common/form.js:236 +#: src/common/form.js:245 +#: src/common/form.js:254 +#: src/common/form.js:264 +#: src/common/form.js:275 +#: src/common/form.js:289 +#: src/common/form.js:340 +#: src/common/form.js:350 +#: src/common/form.js:366 +#: src/common/form.js:378 +#: src/common/form.js:398 +msgid "This field is required" +msgstr "Šis lauks ir obligāts" + +#: src/component/settings/webdav.vue:58 +msgid "This mounts the originals folder as a network drive and allows you to open, edit, and delete files from your computer or smartphone as if they were local." +msgstr "Tas pievieno oriģinālu mapi kā tīkla disku un ļauj atvērt, rediģēt un dzēst failus no datora vai viedtālruņa tā, it kā tie būtu lokāli." + +#: src/page/settings/services.vue:85 +msgid "This mounts the originals folder as a network drive and allows you to open, edit, and delete files from your computer or smartphone as if they were local. " +msgstr "Tas pievieno oriģinālu mapi kā tīkla disku un ļauj atvērt, rediģēt un dzēst failus no datora vai viedtālruņa tā, it kā tie būtu lokāli. " + +#: src/page/admin/activity.vue:124 +#: src/locales.js:349 +msgid "Time" +msgstr "Laiks" + +#: src/component/photo/edit/details.vue:122 +msgid "Time UTC" +msgstr "Laiks UTC" + +#: src/component/photo/batch-edit.vue:295 +#: src/component/photo/edit/details.vue:139 +#: src/page/settings/general.vue:47 +msgid "Time Zone" +msgstr "Laika josla" + +#: src/component/service/edit.vue:214 +msgid "Timeout" +msgstr "Taimauts" + +#: src/common/util.js:808 +#: src/component/photo/view/list.vue:47 +msgid "Title" +msgstr "Nosaukums" + +#: src/page/settings/account.vue:291 +#: src/page/settings/account.vue:293 +msgid "Title / Position" +msgstr "Amats/Amats" + +#: src/page/albums.vue:440 +msgid "Title too long" +msgstr "Nosaukums ir pārāk garš" + +#: src/locales.js:239 +msgid "to" +msgstr "uz" + +#: src/component/settings/passcode.vue:161 +msgid "To avoid being locked out of your account, please download, print or copy this recovery code now and keep it in a safe place." +msgstr "Lai izvairītos no konta bloķēšanas, lūdzu, lejupielādējiet, izdrukājiet vai kopējiet šo atkopšanas kodu tūlīt un glabājiet to drošā vietā." + +#: src/component/settings/apps.vue:123 +msgid "To generate a new app-specific password, please enter the name and authorization scope of the application and select an expiration date:" +msgstr "Lai ģenerētu jaunu lietotnei paredzētu paroli, lūdzu, ievadiet lietojumprogrammas nosaukumu un autorizācijas darbības jomu un atlasiet derīguma termiņu:" + +#: src/component/settings/passcode.vue:204 +msgid "To switch to a new authenticator app or device, first deactivate two-factor authentication and then reactivate it:" +msgstr "Lai pārslēgtos uz jaunu autentifikatora lietotni vai ierīci, vispirms deaktivizējiet divfaktoru autentifikāciju un pēc tam to atkārtoti aktivizējiet:" + +#: src/page/connect.vue:114 +#: src/page/connect.vue:125 +msgid "To upgrade, you can either enter an activation code or click \"Register\" to sign up on our website:" +msgstr "Lai jauninātu, varat ievadīt aktivizācijas kodu vai noklikšķināt uz \"Reģistrēties\", lai reģistrētos mūsu tīmekļa vietnē:" + +#: src/locales.js:263 +msgid "Today" +msgstr "Šodien" + +#: src/component/album/toolbar.vue:28 +#: src/component/photo/toolbar.vue:55 +msgid "Toggle View" +msgstr "Pārslēgt skatu" + +#: src/component/share/dialog.vue:89 +msgid "Token" +msgstr "Žetons" + +#: src/component/upload/dialog.vue:342 +msgid "Too many files selected" +msgstr "Atlasīts pārāk daudz failu" + +#: src/common/api.js:124 +msgid "Too many requests" +msgstr "Pārāk daudz pieprasījumu" + +#: src/component/photo/toolbar.vue:493 +#: src/page/library/errors.vue:238 +msgid "Troubleshooting" +msgstr "Problēmu novēršana" + +#: src/component/photo/view/cards.vue:17 +#: src/component/photo/view/list.vue:17 +#: src/component/photo/view/list.vue:212 +#: src/component/photo/view/mosaic.vue:17 +#: src/page/albums.vue:166 +#: src/page/labels.vue:73 +#: src/page/people/recognized.vue:86 +msgid "Try again using other filters or keywords." +msgstr "Mēģiniet vēlreiz, izmantojot citus filtrus vai atslēgvārdus." + +#: src/component/settings/passcode.vue:192 +msgid "Two-factor authentication has been enabled for your account." +msgstr "Jūsu kontam ir iespējota divfaktoru autentifikācija." + +#: src/component/photo/batch-edit.vue:393 +#: src/component/photo/edit/files.vue:119 +#: src/component/photo/edit/files.vue:120 +#: src/component/photo/edit/files.vue:126 +#: src/component/photo/edit/files.vue:457 +#: src/component/photo/edit/info.vue:25 +#: src/component/service/edit.vue:200 +msgid "Type" +msgstr "Tips" + +#: src/component/album/toolbar.vue:258 +msgid "Unable to delete" +msgstr "Nevar izdzēst" + +#: src/options/auth.js:14 +msgid "Unauthorized" +msgstr "Neautorizēts" + +#: src/component/photo/edit/people.vue:53 +msgid "Undo" +msgstr "Atsaukt" + +#: src/page/settings/content.vue:98 +msgid "Unique ID" +msgstr "Unikāls ID" + +#: src/page/settings/account.vue:221 +#: src/page/settings/account.vue:241 +#: src/page/settings/account.vue:261 +#: src/common/util.js:552 +#: src/component/photo/edit/details.vue:102 +#: src/component/photo/edit/details.vue:460 +#: src/component/photo/edit/details.vue:59 +#: src/component/photo/edit/details.vue:81 +#: src/component/sidebar/info.vue:120 +#: src/model/album.js:155 +#: src/model/photo.js:1007 +#: src/model/photo.js:1029 +#: src/model/photo.js:1074 +#: src/model/photo.js:1087 +#: src/model/photo.js:307 +#: src/model/photo.js:797 +#: src/model/photo.js:822 +#: src/model/photo.js:845 +#: src/model/photo.js:863 +#: src/model/photo.js:910 +#: src/model/photo.js:918 +#: src/model/user.js:137 +#: src/options/options.js:106 +#: src/options/options.js:124 +#: src/options/options.js:141 +#: src/options/options.js:155 +#: src/options/options.js:167 +#: src/page/library/errors.vue:402 +#: src/page/library/errors.vue:409 +#: src/page/settings/account.vue:221 +#: src/page/settings/account.vue:241 +#: src/page/settings/account.vue:261 +msgid "Unknown" +msgstr "Nezināms" + +#: src/page/settings/account.vue:467 +#: src/page/settings/account.vue:475 +#: src/component/navigation.vue:1057 +#: src/page/settings/account.vue:400 +#: src/page/settings/account.vue:408 +msgid "Unregistered" +msgstr "Nereģistrēts" + +#: src/app/routes.js:244 +#: src/component/navigation.vue:289 +msgid "Unsorted" +msgstr "Nešķirots" + +#: src/component/photo/edit/files.vue:58 +#: src/component/photo/edit/files.vue:66 +#: src/component/photo/edit/files.vue:80 +msgid "Unstack" +msgstr "Atdalīt" + +#: src/component/photo/edit/files.vue:286 +#: src/component/photo/edit/files.vue:287 +#: src/component/photo/edit/files.vue:293 +#: src/component/photo/edit/info.vue:214 +#: src/component/photo/edit/info.vue:215 +msgid "Updated" +msgstr "Atjaunināts" + +#: src/page/library/index.vue:262 +msgid "Updating faces" +msgstr "Seju atjaunināšana" + +#: src/page/library/index.vue:268 +msgid "Updating index" +msgstr "Indeksa atjaunināšana" + +#: src/page/library/index.vue:260 +msgid "Updating moments" +msgstr "Atjaunināšanas mirkļi" + +#: src/component/user/edit/dialog.vue:485 +msgid "Updating picture…" +msgstr "Notiek attēla atjaunināšana…" + +#: src/page/library/index.vue:264 +msgid "Updating previews" +msgstr "Priekšskatījumu atjaunināšana" + +#: src/page/library/index.vue:258 +msgid "Updating stacks" +msgstr "Kaudzīšu atjaunināšana" + +#: src/page/connect.vue:20 +#: src/component/navigation.vue:418 +#: src/component/navigation.vue:419 +#: src/component/navigation.vue:443 +#: src/component/navigation.vue:449 +#: src/component/navigation.vue:464 +#: src/component/navigation.vue:695 +#: src/component/navigation.vue:703 +#: src/component/navigation.vue:726 +#: src/component/navigation.vue:734 +#: src/component/navigation.vue:781 +#: src/component/navigation.vue:964 +#: src/page/connect.vue:22 +msgid "Upgrade" +msgstr "Jaunināt" + +#: src/page/about/about.vue:54 +#: src/page/connect.vue:95 +#: src/component/confirm/sponsor.vue:28 +#: src/component/confirm/sponsor.vue:29 +#: src/component/confirm/sponsor.vue:38 +#: src/component/confirm/sponsor.vue:46 +#: src/page/about/about.vue:63 +#: src/page/connect.vue:97 +msgid "Upgrade Now" +msgstr "Jaunināt tūlīt" + +#: src/component/album/toolbar.vue:193 +#: src/component/navigation.vue:884 +#: src/component/photo/toolbar.vue:475 +#: src/component/service/upload.vue:28 +#: src/component/service/upload.vue:29 +#: src/component/service/upload.vue:38 +#: src/component/service/upload.vue:63 +#: src/component/service/upload.vue:70 +#: src/component/upload/dialog.vue:173 +#: src/page/albums.vue:556 +#: src/page/labels.vue:351 +#: src/page/library/import.vue:69 +#: src/page/library/import.vue:75 +#: src/page/settings/general.vue:155 +#: src/page/settings/services.vue:184 +msgid "Upload" +msgstr "Augšupielādēt" + +#: src/component/upload/dialog.vue:36 +msgid "Upload complete. Indexing…" +msgstr "Augšupielāde pabeigta. Indeksēšana…" + +#: src/component/upload/dialog.vue:32 +#: src/component/upload/dialog.vue:426 +msgid "Upload failed" +msgstr "Augšupielāde neizdevās" + +#: src/component/service/edit.vue:129 +msgid "Upload local files" +msgstr "Augšupielādēt lokālos failus" + +#: src/component/user/edit/dialog.vue:260 +#: src/component/user/edit/dialog.vue:261 +msgid "Upload Path" +msgstr "Augšupielādes ceļš" + +#: src/component/upload/dialog.vue:34 +msgid "Uploading %{n} of %{t}…" +msgstr "Notiek %{n} no %{t} augšupielāde…" + +#: src/component/upload/dialog.vue:254 +#: src/component/upload/dialog.vue:262 +#: src/component/upload/dialog.vue:378 +msgid "Uploading photos…" +msgstr "Notiek fotoattēlu augšupielāde…" + +#: src/page/settings/account.vue:555 +#: src/page/settings/account.vue:488 +msgid "Uploading…" +msgstr "Notiek augšupielāde…" + +#: src/component/upload/dialog.vue:98 +msgid "Uploads that may contain such images will be rejected automatically." +msgstr "Augšupielādes, kas var saturēt šādus attēlus, tiks automātiski noraidītas." + +#: src/page/settings/advanced.vue:394 +msgid "Use Presets" +msgstr "Izmantojiet sākotnējos iestatījumus" + +#: src/component/settings/passcode.vue:139 +msgid "Use the following recovery code to access your account when you are unable to generate a valid verification code with your authenticator app:" +msgstr "Ja autentifikācijas lietotnē nevarat ģenerēt derīgu verifikācijas kodu, izmantojiet šo atkopšanas kodu, lai piekļūtu savam kontam:" + +#: src/page/auth/login.vue:139 +#: src/page/auth/login.vue:160 +#: src/page/auth/login.vue:47 +#: src/page/auth/login.vue:48 +#: src/page/auth/login.vue:55 +#: src/page/auth/login.vue:69 +msgid "Use your recovery code or contact an administrator for help." +msgstr "Izmantojiet savu atkopšanas kodu vai sazinieties ar administratoru, lai saņemtu palīdzību." + +#: src/options/admin.js:14 +#: src/page/admin/sessions.vue:138 +#: src/page/admin/sessions.vue:291 +#: src/locales.js:329 +#: src/model/user.js:365 +#: src/options/auth.js:8 +msgid "User" +msgstr "Lietotājs" + +#: src/component/navigation.vue:957 +msgid "User Guide" +msgstr "Lietotāja rokasgrāmata" + +#: src/page/settings/general.vue:6 +msgid "User Interface" +msgstr "Lietotāja saskarne" + +#: src/component/user/add/dialog.vue:34 +#: src/component/user/add/dialog.vue:35 +#: src/page/admin/sessions.vue:131 +#: src/page/admin/users.vue:266 +#: src/component/service/add.vue:38 +#: src/component/service/edit.vue:172 +#: src/component/share/dialog.vue:174 +#: src/locales.js:320 +msgid "Username" +msgstr "Lietotājvārds" + +#: src/component/navigation.vue:400 +#: src/component/navigation.vue:401 +#: src/component/navigation.vue:425 +#: src/component/navigation.vue:431 +#: src/component/navigation.vue:446 +#: src/component/navigation.vue:677 +#: src/component/navigation.vue:685 +#: src/component/navigation.vue:693 +#: src/locales.js:330 +msgid "Users" +msgstr "Lietotāji" + +#: src/component/photo/view/cards.vue:144 +#: src/component/photo/view/cards.vue:298 +#: src/component/photo/view/mosaic.vue:91 +#: src/options/options.js:366 +msgid "Vector" +msgstr "Vektors" + +#: src/component/navigation.vue:119 +#: src/component/navigation.vue:125 +#: src/component/navigation.vue:140 +#: src/component/navigation.vue:155 +#: src/component/navigation.vue:173 +#: src/component/navigation.vue:180 +#: src/component/navigation.vue:94 +#: src/component/navigation.vue:95 +msgid "Vectors" +msgstr "Vektori" + +#: src/component/settings/passcode.vue:111 +#: src/page/auth/login.vue:69 +msgid "Verification Code" +msgstr "Verifikācijas kods" + +#: src/locales.js:322 +msgid "Version" +msgstr "Versija" + +#: src/component/photo/batch-edit.vue:166 +#: src/component/photo/batch-edit.vue:84 +#: src/component/photo/view/cards.vue:161 +#: src/component/photo/view/cards.vue:271 +#: src/component/photo/view/list.vue:96 +#: src/component/photo/view/mosaic.vue:108 +#: src/model/file.js:197 +#: src/model/photo.js:934 +#: src/model/photo.js:952 +#: src/options/options.js:354 +msgid "Video" +msgstr "Video" + +#: src/component/album/edit/dialog.vue:149 +#: src/component/photo/toolbar.vue:417 +#: src/component/photo/toolbar.vue:428 +#: src/component/photo/toolbar.vue:439 +msgid "Video Duration" +msgstr "Video ilgums" + +#: src/app/routes.js:272 +#: src/component/navigation.vue:184 +#: src/component/navigation.vue:185 +#: src/component/navigation.vue:209 +#: src/component/navigation.vue:215 +#: src/component/navigation.vue:230 +#: src/component/navigation.vue:324 +#: src/options/options.js:259 +msgid "Videos" +msgstr "Video" + +#: src/component/photo/toolbar.vue:153 +#: src/component/photo/view/cards.vue:156 +#: src/component/photo/view/mosaic.vue:103 +msgid "View" +msgstr "Skatīt" + +#: src/page/settings/content.vue:142 +msgid "View search results as a list." +msgstr "Skatīt meklēšanas rezultātus kā sarakstu." + +#: src/options/admin.js:18 +#: src/options/auth.js:9 +msgid "Viewer" +msgstr "Skatītājs" + +#: src/common/util.js:816 +msgid "Vision" +msgstr "Vīzija" + +#: src/page/admin/sessions.vue:148 +#: src/options/auth.js:13 +msgid "Visitor" +msgstr "Apmeklētājs" + +#: src/component/photo/toolbar.vue:440 +msgid "Visual Similarity" +msgstr "Vizuālā līdzība" + +#: src/page/library/errors.vue:395 +msgid "Warning" +msgstr "Brīdinājums" + +#: src/page/about/feedback.vue:18 +#: src/page/about/feedback.vue:19 +msgid "We appreciate your feedback!" +msgstr "Mēs novērtējam jūsu atsauksmes!" + +#: src/page/about/feedback.vue:26 +#: src/page/about/feedback.vue:27 +msgid "We do our best to respond within five business days or less." +msgstr "Mēs darām visu iespējamo, lai atbildētu piecu darba dienu laikā vai ātrāk." + +#: src/component/user/add/dialog.vue:142 +#: src/component/user/edit/dialog.vue:281 +#: src/page/admin/users.vue:275 +#: src/locales.js:334 +msgid "Web Login" +msgstr "Tīmekļa pieteikšanās" + +#: src/component/user/add/dialog.vue:151 +#: src/component/user/edit/dialog.vue:291 +#: src/page/admin/users.vue:277 +#: src/options/auth.js:56 +#: src/options/auth.js:69 +#: src/options/options.js:505 +msgid "WebDAV" +msgstr "WebDAV" + +#: src/component/settings/webdav.vue:19 +msgid "WebDAV clients can connect to PhotoPrism using the following URL:" +msgstr "WebDAV klienti var izveidot savienojumu ar PhotoPrism, izmantojot šo URL:" + +#: src/page/settings/services.vue:80 +msgid "WebDAV clients, like Microsoft’s Windows Explorer or Apple's Finder, can connect directly to PhotoPrism. " +msgstr "WebDAV klienti, piemēram, Microsoft Windows Explorer vai Apple Finder, var tieši izveidot savienojumu ar PhotoPrism. " + +#: src/component/service/upload.vue:16 +#: src/component/share/dialog.vue:139 +msgid "WebDAV Upload" +msgstr "WebDAV augšupielāde" + +#: src/page/places.vue:459 +msgid "WebGL support is disabled in your browser" +msgstr "Jūsu pārlūkprogrammā ir atspējots WebGL atbalsts." + +#: src/page/settings/account.vue:335 +#: src/page/settings/account.vue:391 +#: src/page/settings/account.vue:324 +msgid "Website" +msgstr "Tīmekļa vietne" + +#: src/options/options.js:456 +msgid "White" +msgstr "Balts" + +#: src/page/settings/account.vue:279 +msgid "Work Details" +msgstr "Darba detaļas" + +#: src/page/settings/account.vue:260 +#: src/component/photo/batch-edit.vue:276 +#: src/component/photo/edit/details.vue:101 +#: src/component/photo/toolbar.vue:191 +#: src/page/albums.vue:71 +#: src/page/settings/account.vue:260 +msgid "Year" +msgstr "Gads" + +#: src/options/options.js:449 +msgid "Yellow" +msgstr "Dzeltens" + +#: src/component/confirm/dialog.vue:12 +#: src/component/confirm/dialog.vue:13 +#: src/component/confirm/dialog.vue:26 +#: src/component/people/merge/dialog.vue:22 +#: src/component/photo/archive/dialog.vue:22 +#: src/component/photo/batch-edit.vue:1086 +#: src/component/photo/clipboard.vue:168 +#: src/component/photo/edit/files.vue:152 +#: src/component/photo/edit/files.vue:153 +#: src/component/photo/edit/files.vue:159 +#: src/component/photo/edit/files.vue:184 +#: src/component/photo/edit/files.vue:185 +#: src/component/photo/edit/files.vue:191 +#: src/component/photo/edit/files.vue:192 +#: src/component/photo/edit/files.vue:193 +#: src/component/photo/edit/files.vue:199 +#: src/component/photo/edit/files.vue:200 +#: src/component/photo/edit/files.vue:201 +#: src/component/photo/edit/files.vue:207 +#: src/component/photo/edit/files.vue:271 +#: src/component/photo/edit/files.vue:272 +#: src/component/photo/edit/files.vue:278 +#: src/component/photo/edit/info.vue:167 +#: src/component/photo/edit/info.vue:181 +#: src/component/photo/edit/info.vue:195 +#: src/component/photo/edit/info.vue:209 +#: src/component/photo/edit/info.vue:223 +#: src/component/photo/toolbar.vue:288 +msgid "Yes" +msgstr "Jā" + +#: src/component/confirm/sponsor.vue:28 +#: src/page/about/about.vue:94 +#: src/page/connect.vue:193 +msgid "You are welcome to contact us at membership@photoprism.app for questions regarding your membership." +msgstr "Ja jums ir jautājumi par savu dalību, lūdzu, sazinieties ar mums pa e-pastu membership@photoprism.app." + +#: src/component/album/clipboard.vue:223 +#: src/component/people/clipboard.vue:140 +msgid "You can only download one album" +msgstr "Varat lejupielādēt tikai vienu albumu" + +#: src/component/label/clipboard.vue:163 +msgid "You can only download one label" +msgstr "Varat lejupielādēt tikai vienu etiķeti" + +#: src/component/location/dialog.vue:102 +msgid "You can search for a location or move the marker on the map to change the position:" +msgstr "Varat meklēt atrašanās vietu vai pārvietot marķieri kartē, lai mainītu pozīciju:" + +#: src/component/upload/dialog.vue:93 +msgid "You can upload up to %{n} files for test purposes." +msgstr "Testēšanas nolūkos varat augšupielādēt līdz %{n} failiem." + +#: src/component/album/clipboard.vue:163 +#: src/component/album/clipboard.vue:174 +msgid "You may only select one item" +msgstr "Varat izvēlēties tikai vienu vienumu" + +#: src/component/photo/edit/people.vue:15 +#: src/page/people/new.vue:46 +#: src/page/people/recognized.vue:87 +msgid "You may rescan your library to find additional faces." +msgstr "Varat atkārtoti skenēt savu bibliotēku, lai atrastu papildu sejas." + +#: src/page/connect.vue:63 +#: src/page/connect.vue:65 +msgid "Your account has been successfully connected." +msgstr "Jūsu konts ir veiksmīgi pievienots." + +#: src/page/places.vue:463 +msgid "Your browser does not support WebGL" +msgstr "Jūsu pārlūkprogramma neatbalsta WebGL" + +#: src/component/confirm/sponsor.vue:18 +#: src/page/about/about.vue:51 +#: src/page/about/about.vue:89 +#: src/page/connect.vue:116 +msgid "Your continued support helps us provide regular updates and remain independent, so we can fulfill our mission and protect your privacy." +msgstr "Jūsu pastāvīgais atbalsts palīdz mums regulāri atjaunināt informāciju un saglabāt neatkarību, lai mēs varētu pildīt savu misiju un aizsargāt jūsu privātumu." + +#: src/page/albums.vue:176 +msgid "Your library is continuously analyzed to automatically create albums of special moments, trips, and places." +msgstr "Jūsu bibliotēka tiek nepārtraukti analizēta, lai automātiski izveidotu albumus ar īpašiem mirkļiem, ceļojumiem un vietām." + +#: src/component/lightbox.vue:412 +msgid "Zoom in/out" +msgstr "Pietuvināt/attālināt" + +#: src/component/user/edit/dialog.vue:38 +#: src/component/user/edit/dialog.vue:39 +#: src/page/settings/account.vue:32 +#: src/page/settings/account.vue:34 +#: src/page/settings/account.vue:32 +#: src/page/settings/account.vue:34 +msgctxt "Account" +msgid "Title" +msgstr "Nosaukums" + +#: src/component/photo/batch-edit.vue:372 +msgctxt "Edit" +msgid "Content" +msgstr "Saturs" + +#: src/app/routes.js:317 +#: src/component/navigation.vue:127 +#: src/component/navigation.vue:128 +#: src/component/navigation.vue:152 +#: src/component/navigation.vue:158 +#: src/component/navigation.vue:173 +#: src/component/navigation.vue:188 +#: src/component/navigation.vue:240 +#: src/component/navigation.vue:246 +#: src/page/settings/general.vue:260 +msgctxt "Noun" +msgid "Archive" +msgstr "Arhīvs" + +#: src/component/photo/batch-edit.vue:203 +#: src/component/photo/edit/details.vue:31 +#: src/component/photo/edit/info.vue:87 +#: src/component/sidebar/info.vue:14 +msgctxt "Photo" +msgid "Title" +msgstr "Nosaukums" + +#: src/component/lightbox.vue:1449 +#: src/component/photo/clipboard.vue:116 +#: src/component/photo/view/cards.vue:204 +msgctxt "Verb" +msgid "Archive" +msgstr "Arhīvēt" diff --git a/frontend/tests/vitest/common/config.test.js b/frontend/tests/vitest/common/config.test.js index 8a4da1547..8c31704ee 100644 --- a/frontend/tests/vitest/common/config.test.js +++ b/frontend/tests/vitest/common/config.test.js @@ -1,4 +1,4 @@ -import { describe, it, expect } from "vitest"; +import { describe, it, expect, beforeEach, afterEach } from "vitest"; import "../fixtures"; import Config from "common/config"; import StorageShim from "node-storage-shim"; @@ -11,7 +11,31 @@ const createTestConfig = () => { return new Config(new StorageShim(), values); }; +const resetThemesToDefault = () => { + themes.SetOptions([ + { + text: "Default", + value: "default", + disabled: false, + }, + ]); + + themes.Set("default", { + name: "default", + title: "Default", + colors: {}, + variables: {}, + }); +}; + describe("common/config", () => { + beforeEach(() => { + resetThemesToDefault(); + }); + + afterEach(() => { + resetThemesToDefault(); + }); it("should get all config values", () => { const storage = new StorageShim(); const values = { siteTitle: "Foo", name: "testConfig", year: "2300" }; @@ -116,42 +140,12 @@ describe("common/config", () => { variables: {}, }; - themes.SetOptions([ - { - text: "Default", - value: "default", - disabled: false, - }, - ]); - - themes.Set("default", { - name: "default", - title: "Default", - colors: {}, - variables: {}, - }); - themes.Assign([forcedTheme]); cfg.setTheme("default"); expect(cfg.themeName).toBe("portal-forced"); expect(cfg.theme.colors.background).toBe("#111111"); - - themes.Remove("portal-forced"); - themes.SetOptions([ - { - text: "Default", - value: "default", - disabled: false, - }, - ]); - themes.Set("default", { - name: "default", - title: "Default", - colors: {}, - variables: {}, - }); }); it("should return app edition", () => { diff --git a/frontend/tests/vitest/component/navigation.test.js b/frontend/tests/vitest/component/navigation.test.js new file mode 100644 index 000000000..37dd6a035 --- /dev/null +++ b/frontend/tests/vitest/component/navigation.test.js @@ -0,0 +1,370 @@ +import { describe, it, expect, vi, afterEach } from "vitest"; +import { shallowMount, config as VTUConfig } from "@vue/test-utils"; +import PNavigation from "component/navigation.vue"; + +function mountNavigation({ + routeName = "photos", + routeMeta = { hideNav: false }, + isPublic = false, + isRestricted = false, + sessionAuth = true, + featureOverrides = {}, + configValues = {}, + allowMock, + routerPush, + vuetifyDisplay = { smAndDown: false }, + eventPublish, + utilOverrides = {}, + sessionOverrides = {}, +} = {}) { + const baseConfig = VTUConfig.global.mocks.$config || {}; + const baseEvent = VTUConfig.global.mocks.$event || {}; + const baseUtil = VTUConfig.global.mocks.$util || {}; + const baseNotify = VTUConfig.global.mocks.$notify || {}; + + const featureFlags = { + files: true, + settings: true, + upload: true, + account: true, + logs: true, + library: true, + places: true, + ...featureOverrides, + }; + + const values = { + siteUrl: "http://localhost:2342/", + usage: { filesTotal: 1024, filesUsed: 512 }, + legalUrl: configValues.legalUrl ?? null, + legalInfo: configValues.legalInfo ?? "", + disable: { settings: false }, + count: {}, + ...configValues, + }; + + const configMock = { + ...baseConfig, + getName: baseConfig.getName || vi.fn(() => "PhotoPrism"), + getAbout: baseConfig.getAbout || vi.fn(() => "About"), + getIcon: baseConfig.getIcon || vi.fn(() => "/icon.png"), + getTier: baseConfig.getTier || vi.fn(() => 1), + isPro: baseConfig.isPro || vi.fn(() => false), + isSponsor: baseConfig.isSponsor || vi.fn(() => false), + get: vi.fn((key) => { + if (key === "demo") return false; + if (key === "public") return isPublic; + if (key === "readonly") return false; + return false; + }), + feature: vi.fn((name) => { + if (name in featureFlags) { + return !!featureFlags[name]; + } + return true; + }), + allow: allowMock || baseConfig.allow || vi.fn(() => true), + deny: vi.fn((resource, action) => (resource === "photos" && action === "access_library" ? isRestricted : false)), + values, + disconnected: false, + page: { title: "Photos" }, + test: false, + }; + + const session = { + auth: sessionAuth, + isAdmin: vi.fn(() => true), + isSuperAdmin: vi.fn(() => true), + hasScope: vi.fn(() => false), + getUser: vi.fn(() => ({ + getDisplayName: vi.fn(() => "Test User"), + getAccountInfo: vi.fn(() => "test@example.com"), + getAvatarURL: vi.fn(() => "/avatar.jpg"), + })), + logout: vi.fn(), + ...sessionOverrides, + }; + + const publish = eventPublish || baseEvent.publish || vi.fn(); + + const eventBus = { + ...baseEvent, + publish, + subscribe: baseEvent.subscribe || vi.fn(() => "sub-id"), + unsubscribe: baseEvent.unsubscribe || vi.fn(), + }; + + const notify = { + ...baseNotify, + info: baseNotify.info || vi.fn(), + blockUI: baseNotify.blockUI || vi.fn(), + }; + + const util = { + ...baseUtil, + openExternalUrl: vi.fn(), + gigaBytes: vi.fn((bytes) => bytes), + ...utilOverrides, + }; + + const push = routerPush || vi.fn(); + + const wrapper = shallowMount(PNavigation, { + global: { + mocks: { + $config: configMock, + $session: session, + $router: { push }, + $route: { name: routeName, meta: routeMeta }, + $vuetify: { display: { smAndDown: !!vuetifyDisplay.smAndDown } }, + $event: eventBus, + $util: util, + $notify: notify, + $isRtl: false, + }, + stubs: { + "router-link": { template: "" }, + }, + }, + }); + + return { + wrapper, + configMock, + session, + eventBus, + notify, + util, + push, + }; +} + +describe("component/navigation", () => { + afterEach(() => { + vi.restoreAllMocks(); + }); + + describe("routeName", () => { + it("returns true when current route starts with given name", () => { + const { wrapper } = mountNavigation({ routeName: "photos_browse" }); + expect(wrapper.vm.routeName("photos")).toBe(true); + expect(wrapper.vm.routeName("albums")).toBe(false); + }); + + it("returns false when name or route name is missing", () => { + const { wrapper } = mountNavigation({ routeName: "" }); + expect(wrapper.vm.routeName("photos")).toBe(false); + expect(wrapper.vm.routeName("")).toBe(false); + }); + }); + + describe("auth and visibility", () => { + it("auth is true when session is authenticated", () => { + const { wrapper } = mountNavigation({ sessionAuth: true, isPublic: false }); + expect(wrapper.vm.auth).toBe(true); + }); + + it("auth is true when instance is public even without session", () => { + const { wrapper } = mountNavigation({ sessionAuth: false, isPublic: true }); + expect(wrapper.vm.auth).toBe(true); + }); + + it("auth is false when neither session nor public access is available", () => { + const { wrapper } = mountNavigation({ sessionAuth: false, isPublic: false }); + expect(wrapper.vm.auth).toBe(false); + }); + + it("visible is false when route meta.hideNav is true", () => { + const { wrapper } = mountNavigation({ routeMeta: { hideNav: true } }); + expect(wrapper.vm.visible).toBe(false); + }); + }); + + describe("drawer behavior", () => { + it("toggleDrawer toggles drawer on small screens", () => { + const { wrapper } = mountNavigation({ + vuetifyDisplay: { smAndDown: true }, + sessionAuth: true, + }); + + // Force small-screen mode and authenticated session + wrapper.vm.$vuetify.display.smAndDown = true; + wrapper.vm.session.auth = true; + wrapper.vm.isPublic = false; + + wrapper.vm.drawer = false; + wrapper.vm.toggleDrawer({ target: {} }); + expect(wrapper.vm.drawer).toBe(true); + + wrapper.vm.toggleDrawer({ target: {} }); + expect(wrapper.vm.drawer).toBe(false); + }); + + it("toggleDrawer toggles mini mode on desktop", () => { + const { wrapper } = mountNavigation({ + vuetifyDisplay: { smAndDown: false }, + isRestricted: false, + }); + + const initial = wrapper.vm.isMini; + wrapper.vm.toggleDrawer({ target: {} }); + expect(wrapper.vm.isMini).toBe(!initial); + }); + + it("toggleIsMini respects restricted mode and updates localStorage", () => { + const setItemSpy = vi.spyOn(Storage.prototype, "setItem"); + + const { wrapper } = mountNavigation({ isRestricted: false }); + const initial = wrapper.vm.isMini; + + wrapper.vm.toggleIsMini(); + expect(wrapper.vm.isMini).toBe(!initial); + expect(setItemSpy).toHaveBeenCalledWith("navigation.mode", `${!initial}`); + + wrapper.vm.isRestricted = true; + const before = wrapper.vm.isMini; + wrapper.vm.toggleIsMini(); + expect(wrapper.vm.isMini).toBe(before); + }); + }); + + describe("account and legal navigation", () => { + it("showAccountSettings routes to account settings when account feature is enabled", () => { + const { wrapper, push } = mountNavigation({ + featureOverrides: { account: true }, + }); + + wrapper.vm.showAccountSettings(); + expect(push).toHaveBeenCalledWith({ name: "settings_account" }); + }); + + it("showAccountSettings falls back to general settings when account feature is disabled", () => { + const { wrapper, push } = mountNavigation({ + featureOverrides: { account: false }, + }); + + wrapper.vm.showAccountSettings(); + expect(push).toHaveBeenCalledWith({ name: "settings" }); + }); + + it("showLegalInfo opens external URL when legalUrl is configured", () => { + const { wrapper, util } = mountNavigation({ + configValues: { legalUrl: "https://example.com/legal" }, + }); + + wrapper.vm.showLegalInfo(); + expect(util.openExternalUrl).toHaveBeenCalledWith("https://example.com/legal"); + }); + + it("showLegalInfo routes to about page when legalUrl is missing", () => { + const { wrapper, push } = mountNavigation({ + configValues: { legalUrl: null }, + }); + + wrapper.vm.showLegalInfo(); + expect(push).toHaveBeenCalledWith({ name: "about" }); + }); + }); + + describe("home and upload actions", () => { + it("onHome toggles drawer on small screens and does not navigate", () => { + const { wrapper, push } = mountNavigation({ + vuetifyDisplay: { smAndDown: true }, + routeName: "browse", + }); + + // Ensure mobile mode and authenticated session so drawer logic runs + wrapper.vm.$vuetify.display.smAndDown = true; + wrapper.vm.session.auth = true; + wrapper.vm.isPublic = false; + wrapper.vm.drawer = false; + + wrapper.vm.onHome({ target: {} }); + expect(wrapper.vm.drawer).toBe(true); + expect(push).not.toHaveBeenCalled(); + }); + + it("onHome navigates to home on desktop when not already there", () => { + const { wrapper, push } = mountNavigation({ + vuetifyDisplay: { smAndDown: false }, + routeName: "albums", + }); + + // Force desktop mode explicitly to avoid relying on Vuetify defaults + wrapper.vm.$vuetify.display.smAndDown = false; + + wrapper.vm.onHome({ target: {} }); + expect(push).toHaveBeenCalledWith({ name: "home" }); + }); + + it("openUpload publishes dialog.upload event", () => { + const publish = vi.fn(); + const { wrapper, eventBus } = mountNavigation({ eventPublish: publish }); + + wrapper.vm.openUpload(); + expect(eventBus.publish).toHaveBeenCalledWith("dialog.upload"); + }); + }); + + describe("info and usage actions", () => { + it("reloadApp shows info notification and blocks UI", () => { + vi.useFakeTimers(); + const { wrapper, notify } = mountNavigation(); + const setTimeoutSpy = vi.spyOn(global, "setTimeout"); + + wrapper.vm.reloadApp(); + + expect(notify.info).toHaveBeenCalledWith("Reloading…"); + expect(notify.blockUI).toHaveBeenCalled(); + expect(setTimeoutSpy).toHaveBeenCalled(); + vi.useRealTimers(); + }); + + it("showUsageInfo routes to index files", () => { + const { wrapper, push } = mountNavigation(); + wrapper.vm.showUsageInfo(); + expect(push).toHaveBeenCalledWith({ path: "/index/files" }); + }); + + it("showServerConnectionHelp routes to websockets help", () => { + const { wrapper, push } = mountNavigation(); + wrapper.vm.showServerConnectionHelp(); + expect(push).toHaveBeenCalledWith({ path: "/help/websockets" }); + }); + }); + + describe("indexing state", () => { + it("onIndex sets indexing true for file, folder and indexing events", () => { + const { wrapper } = mountNavigation(); + + wrapper.vm.onIndex("index.file"); + expect(wrapper.vm.indexing).toBe(true); + + wrapper.vm.onIndex("index.folder"); + expect(wrapper.vm.indexing).toBe(true); + + wrapper.vm.onIndex("index.indexing"); + expect(wrapper.vm.indexing).toBe(true); + }); + + it("onIndex sets indexing false when completed", () => { + const { wrapper } = mountNavigation(); + + wrapper.vm.indexing = true; + wrapper.vm.onIndex("index.completed"); + expect(wrapper.vm.indexing).toBe(false); + }); + }); + + describe("logout", () => { + it("onLogout calls session.logout", () => { + const logout = vi.fn(); + const { wrapper, session } = mountNavigation({ + sessionOverrides: { logout }, + }); + + wrapper.vm.onLogout(); + expect(session.logout).toHaveBeenCalled(); + }); + }); +}); diff --git a/frontend/tests/vitest/component/photo/batch-edit.test.js b/frontend/tests/vitest/component/photo/batch-edit.test.js index 4608500a0..9ba37e166 100644 --- a/frontend/tests/vitest/component/photo/batch-edit.test.js +++ b/frontend/tests/vitest/component/photo/batch-edit.test.js @@ -1,5 +1,5 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; -import { shallowMount } from "@vue/test-utils"; +import { shallowMount, config as VTUConfig } from "@vue/test-utils"; import { nextTick } from "vue"; import PPhotoBatchEdit from "component/photo/batch-edit.vue"; import * as contexts from "options/contexts"; @@ -146,20 +146,9 @@ describe("component/photo/batch-edit", () => { }, global: { mocks: { - $notify: { - success: vi.fn(), - error: vi.fn(), - }, $lightbox: { openView: vi.fn(), }, - $event: { - subscribe: vi.fn(), - unsubscribe: vi.fn(), - }, - $config: { - feature: vi.fn().mockReturnValue(true), - }, $vuetify: { display: { mdAndDown: false } }, }, stubs: { @@ -202,6 +191,7 @@ describe("component/photo/batch-edit", () => { }); afterEach(() => { + vi.restoreAllMocks(); if (wrapper) { wrapper.unmount(); } @@ -395,8 +385,6 @@ describe("component/photo/batch-edit", () => { expect(ctx.allowEdit).toBe(false); expect(ctx.allowSelect).toBe(false); expect(ctx.context).toBe(contexts.BatchEdit); - - spy.mockRestore(); }); it("should clamp invalid index to first photo", () => { @@ -407,8 +395,6 @@ describe("component/photo/batch-edit", () => { expect(ctx.index).toBe(0); expect(ctx.allowSelect).toBe(false); - - spy.mockRestore(); }); }); diff --git a/frontend/tests/vitest/component/photo/edit/files.test.js b/frontend/tests/vitest/component/photo/edit/files.test.js new file mode 100644 index 000000000..f3154488a --- /dev/null +++ b/frontend/tests/vitest/component/photo/edit/files.test.js @@ -0,0 +1,325 @@ +import { describe, it, expect, vi, afterEach } from "vitest"; +import { shallowMount, config as VTUConfig } from "@vue/test-utils"; +import PTabPhotoFiles from "component/photo/edit/files.vue"; +import Thumb from "model/thumb"; + +function createFile(overrides = {}) { + return { + UID: "file-uid", + Name: "2018/01/dir:with#hash/file.jpg", + FileType: "jpg", + Error: "", + Primary: false, + Sidecar: false, + Root: "/", + Missing: false, + Pages: 0, + Frames: 0, + Duration: 0, + FPS: 0, + Hash: "hash123", + OriginalName: "file.jpg", + ColorProfile: "", + MainColor: "", + Chroma: 0, + CreatedAt: "2023-01-01T12:00:00Z", + CreatedIn: 1000, + UpdatedAt: "2023-01-02T12:00:00Z", + UpdatedIn: 2000, + thumbnailUrl: vi.fn(() => "/thumb/file.jpg"), + storageInfo: vi.fn(() => "local"), + typeInfo: vi.fn(() => "JPEG"), + sizeInfo: vi.fn(() => "1 MB"), + isAnimated: vi.fn(() => false), + baseName: vi.fn(() => "file.jpg"), + download: vi.fn(), + ...overrides, + }; +} + +function mountPhotoFiles({ + fileOverrides = {}, + featuresOverrides = {}, + experimental = false, + isMobile = false, + modelOverrides = {}, + routerOverrides = {}, +} = {}) { + const baseConfig = VTUConfig.global.mocks.$config || {}; + const baseSettings = baseConfig.getSettings ? baseConfig.getSettings() : { features: {} }; + + const features = { + ...(baseSettings.features || {}), + download: true, + edit: true, + delete: true, + ...featuresOverrides, + }; + + const configMock = { + ...baseConfig, + getSettings: vi.fn(() => ({ + ...baseSettings, + features, + })), + get: vi.fn((key) => { + if (key === "experimental") return experimental; + if (baseConfig.get) { + return baseConfig.get(key); + } + return false; + }), + getTimeZone: baseConfig.getTimeZone || vi.fn(() => "UTC"), + allow: baseConfig.allow || vi.fn(() => true), + values: baseConfig.values || {}, + }; + + const file = createFile(fileOverrides); + + const model = { + fileModels: vi.fn(() => [file]), + deleteFile: vi.fn(() => Promise.resolve()), + unstackFile: vi.fn(), + setPrimaryFile: vi.fn(), + changeFileOrientation: vi.fn(() => Promise.resolve()), + ...modelOverrides, + }; + + const router = { + push: vi.fn(), + resolve: vi.fn((route) => ({ href: route.path || "" })), + ...routerOverrides, + }; + + const lightbox = { + openModels: vi.fn(), + }; + + const baseUtil = VTUConfig.global.mocks.$util || {}; + const util = { + ...baseUtil, + openUrl: vi.fn(), + formatDuration: baseUtil.formatDuration || vi.fn((d) => String(d)), + fileType: baseUtil.fileType || vi.fn((t) => t), + codecName: baseUtil.codecName || vi.fn((c) => c), + formatNs: baseUtil.formatNs || vi.fn((n) => String(n)), + }; + + const wrapper = shallowMount(PTabPhotoFiles, { + props: { + uid: "photo-uid", + }, + global: { + mocks: { + $config: configMock, + $view: { + getData: () => ({ + model, + }), + }, + $router: router, + $lightbox: lightbox, + $util: util, + $isMobile: isMobile, + $gettext: VTUConfig.global.mocks.$gettext || ((s) => s), + $notify: VTUConfig.global.mocks.$notify, + $isRtl: false, + }, + stubs: { + "p-file-delete-dialog": true, + }, + }, + }); + + return { + wrapper, + file, + model, + router, + lightbox, + util, + configMock, + }; +} + +describe("component/photo/edit/files", () => { + afterEach(() => { + vi.restoreAllMocks(); + }); + + describe("action buttons visibility", () => { + it("shows download, primary, unstack and delete buttons for editable JPG file", () => { + const { wrapper } = mountPhotoFiles({ + fileOverrides: { FileType: "jpg", Primary: false, Sidecar: false, Root: "/", Error: "" }, + featuresOverrides: { download: true, edit: true, delete: true }, + }); + + const file = wrapper.vm.view.model.fileModels()[0]; + const { features, experimental, canAccessPrivate } = wrapper.vm; + + // Download button conditions + expect(features.download).toBe(true); + // Primary button conditions + expect(features.edit && (file.FileType === "jpg" || file.FileType === "png") && !file.Error && !file.Primary).toBe(true); + // Unstack button conditions + expect(features.edit && !file.Sidecar && !file.Error && !file.Primary && file.Root === "/").toBe(true); + // Delete button conditions + expect(features.delete && !file.Primary).toBe(true); + // Browse button should not be visible in this scenario + expect(experimental && canAccessPrivate && file.Primary).toBe(false); + }); + + it("shows browse button only for primary file when experimental and private access are enabled", () => { + const { wrapper } = mountPhotoFiles({ + fileOverrides: { Primary: true, Root: "/", FileType: "jpg" }, + experimental: true, + }); + + const file = wrapper.vm.view.model.fileModels()[0]; + const { features, experimental, canAccessPrivate } = wrapper.vm; + + // Browse button conditions + expect(experimental && canAccessPrivate && file.Primary).toBe(true); + // Other actions should not be available for primary file in this scenario + expect(features.edit && (file.FileType === "jpg" || file.FileType === "png") && !file.Error && !file.Primary).toBe(false); + expect(features.edit && !file.Sidecar && !file.Error && !file.Primary && file.Root === "/").toBe(false); + expect(features.delete && !file.Primary).toBe(false); + }); + }); + + describe("openFile", () => { + it("opens file in lightbox using Thumb.fromFile", () => { + const thumbModel = {}; + const { wrapper, file, model, lightbox } = mountPhotoFiles(); + const thumbSpy = vi.spyOn(Thumb, "fromFile").mockReturnValue(thumbModel); + + wrapper.vm.openFile(file); + + expect(thumbSpy).toHaveBeenCalledWith(model, file); + expect(lightbox.openModels).toHaveBeenCalledWith([thumbModel], 0); + }); + }); + + describe("openFolder", () => { + it("emits close and navigates via router.push on mobile", () => { + const { wrapper, router, util, file } = mountPhotoFiles({ + isMobile: true, + fileOverrides: { Name: "2018/01/file.jpg" }, + }); + + wrapper.vm.openFolder(file); + + expect(wrapper.emitted("close")).toBeTruthy(); + expect(router.push).toHaveBeenCalledWith({ path: "/index/files/2018/01" }); + expect(util.openUrl).not.toHaveBeenCalled(); + }); + + it("opens folder in new tab on desktop with encoded path", () => { + const encodedPath = "/index/files/2018/01/dir%3Awith%23hash"; + const resolve = vi.fn((route) => ({ href: route.path })); + const { wrapper, util, file } = mountPhotoFiles({ + isMobile: false, + routerOverrides: { resolve }, + fileOverrides: { Name: "2018/01/dir:with#hash/file.jpg" }, + }); + + wrapper.vm.openFolder(file); + + expect(resolve).toHaveBeenCalledWith({ path: encodedPath }); + expect(util.openUrl).toHaveBeenCalledWith(encodedPath); + }); + }); + + describe("file actions", () => { + it("downloadFile shows notification and calls file.download", async () => { + const { wrapper, file } = mountPhotoFiles(); + const { default: notifyModule } = await import("common/notify"); + const notifySpy = vi.spyOn(notifyModule, "success"); + + wrapper.vm.downloadFile(file); + + expect(notifySpy).toHaveBeenCalledWith("Downloading…"); + expect(file.download).toHaveBeenCalledTimes(1); + }); + + it("unstackFile and setPrimaryFile delegate to model when file is present", () => { + const unstackSpy = vi.fn(); + const setPrimarySpy = vi.fn(); + const { wrapper, file } = mountPhotoFiles({ + modelOverrides: { + unstackFile: unstackSpy, + setPrimaryFile: setPrimarySpy, + }, + }); + + wrapper.vm.unstackFile(file); + wrapper.vm.setPrimaryFile(file); + + expect(unstackSpy).toHaveBeenCalledWith(file.UID); + expect(setPrimarySpy).toHaveBeenCalledWith(file.UID); + + unstackSpy.mockClear(); + setPrimarySpy.mockClear(); + + wrapper.vm.unstackFile(null); + wrapper.vm.setPrimaryFile(null); + + expect(unstackSpy).not.toHaveBeenCalled(); + expect(setPrimarySpy).not.toHaveBeenCalled(); + }); + + it("confirmDeleteFile calls model.deleteFile and closes dialog", async () => { + const deleteFileSpy = vi.fn(() => Promise.resolve()); + const { wrapper, file } = mountPhotoFiles({ + modelOverrides: { + deleteFile: deleteFileSpy, + }, + }); + + wrapper.vm.deleteFile.dialog = true; + wrapper.vm.deleteFile.file = file; + + await wrapper.vm.confirmDeleteFile(); + + expect(deleteFileSpy).toHaveBeenCalledWith(file.UID); + expect(wrapper.vm.deleteFile.dialog).toBe(false); + expect(wrapper.vm.deleteFile.file).toBeNull(); + }); + }); + + describe("changeOrientation", () => { + it("calls model.changeFileOrientation and shows success message", async () => { + const changeOrientationSpy = vi.fn(() => Promise.resolve()); + const { wrapper, file } = mountPhotoFiles({ + modelOverrides: { + changeFileOrientation: changeOrientationSpy, + }, + }); + + const notifySuccessSpy = vi.spyOn(wrapper.vm.$notify, "success"); + + wrapper.vm.changeOrientation(file); + expect(wrapper.vm.busy).toBe(true); + + await Promise.resolve(); + + expect(changeOrientationSpy).toHaveBeenCalledWith(file); + expect(notifySuccessSpy).toHaveBeenCalledWith("Changes successfully saved"); + expect(wrapper.vm.busy).toBe(false); + }); + + it("does nothing when file is missing", () => { + const changeOrientationSpy = vi.fn(() => Promise.resolve()); + const { wrapper } = mountPhotoFiles({ + modelOverrides: { + changeFileOrientation: changeOrientationSpy, + }, + }); + + wrapper.vm.changeOrientation(null); + + expect(changeOrientationSpy).not.toHaveBeenCalled(); + expect(wrapper.vm.busy).toBe(false); + }); + }); +}); diff --git a/frontend/tests/vitest/component/photo/edit/labels.test.js b/frontend/tests/vitest/component/photo/edit/labels.test.js new file mode 100644 index 000000000..ada18747a --- /dev/null +++ b/frontend/tests/vitest/component/photo/edit/labels.test.js @@ -0,0 +1,226 @@ +import { describe, it, expect, vi, afterEach } from "vitest"; +import { shallowMount, config as VTUConfig } from "@vue/test-utils"; +import PTabPhotoLabels from "component/photo/edit/labels.vue"; +import Thumb from "model/thumb"; + +function mountPhotoLabels({ modelOverrides = {}, routerOverrides = {}, utilOverrides = {}, notifyOverrides = {}, viewHasModel = true } = {}) { + const baseConfig = VTUConfig.global.mocks.$config || {}; + const baseNotify = VTUConfig.global.mocks.$notify || {}; + const baseUtil = VTUConfig.global.mocks.$util || {}; + + const model = viewHasModel + ? { + removeLabel: vi.fn(() => Promise.resolve()), + addLabel: vi.fn(() => Promise.resolve()), + activateLabel: vi.fn(), + ...modelOverrides, + } + : null; + + const router = { + push: vi.fn(() => Promise.resolve()), + ...routerOverrides, + }; + + const util = { + ...baseUtil, + sourceName: vi.fn((s) => `source-${s}`), + ...utilOverrides, + }; + + const notify = { + ...baseNotify, + success: baseNotify.success || vi.fn(), + error: baseNotify.error || vi.fn(), + warn: baseNotify.warn || vi.fn(), + ...notifyOverrides, + }; + + const lightbox = { + openModels: vi.fn(), + }; + + const wrapper = shallowMount(PTabPhotoLabels, { + props: { + uid: "photo-uid", + }, + global: { + mocks: { + $config: baseConfig, + $view: { + getData: () => ({ + model, + }), + }, + $router: router, + $util: util, + $notify: notify, + $lightbox: lightbox, + $gettext: VTUConfig.global.mocks.$gettext || ((s) => s), + $isRtl: false, + }, + }, + }); + + return { + wrapper, + model, + router, + util, + notify, + lightbox, + }; +} + +describe("component/photo/edit/labels", () => { + afterEach(() => { + vi.restoreAllMocks(); + }); + + describe("sourceName", () => { + it("delegates to $util.sourceName", () => { + const sourceNameSpy = vi.fn(() => "Human"); + const { wrapper, util } = mountPhotoLabels({ + utilOverrides: { sourceName: sourceNameSpy }, + }); + + const result = wrapper.vm.sourceName("auto"); + + expect(sourceNameSpy).toHaveBeenCalledWith("auto"); + expect(result).toBe("Human"); + // Ensure util on instance is the same object so we actually spied on the right method + expect(wrapper.vm.$util).toBe(util); + }); + }); + + describe("removeLabel", () => { + it("does nothing when label is missing", () => { + const removeSpy = vi.fn(() => Promise.resolve()); + const { wrapper } = mountPhotoLabels({ + modelOverrides: { removeLabel: removeSpy }, + }); + + wrapper.vm.removeLabel(null); + + expect(removeSpy).not.toHaveBeenCalled(); + }); + + it("calls model.removeLabel and shows success message", async () => { + const removeSpy = vi.fn(() => Promise.resolve()); + const notifySuccessSpy = vi.fn(); + const { wrapper } = mountPhotoLabels({ + modelOverrides: { removeLabel: removeSpy }, + notifyOverrides: { success: notifySuccessSpy }, + }); + + const label = { ID: 5, Name: "Cat" }; + + wrapper.vm.removeLabel(label); + await Promise.resolve(); + + expect(removeSpy).toHaveBeenCalledWith(5); + expect(notifySuccessSpy).toHaveBeenCalledWith("removed Cat"); + }); + }); + + describe("addLabel", () => { + it("does nothing when newLabel is empty", () => { + const addSpy = vi.fn(() => Promise.resolve()); + const { wrapper } = mountPhotoLabels({ + modelOverrides: { addLabel: addSpy }, + }); + + wrapper.vm.newLabel = ""; + wrapper.vm.addLabel(); + + expect(addSpy).not.toHaveBeenCalled(); + }); + + it("calls model.addLabel, shows success message and clears newLabel", async () => { + const addSpy = vi.fn(() => Promise.resolve()); + const notifySuccessSpy = vi.fn(); + const { wrapper } = mountPhotoLabels({ + modelOverrides: { addLabel: addSpy }, + notifyOverrides: { success: notifySuccessSpy }, + }); + + wrapper.vm.newLabel = "Dog"; + wrapper.vm.addLabel(); + + await Promise.resolve(); + + expect(addSpy).toHaveBeenCalledWith("Dog"); + expect(notifySuccessSpy).toHaveBeenCalledWith("added Dog"); + expect(wrapper.vm.newLabel).toBe(""); + }); + }); + + describe("activateLabel", () => { + it("does nothing when label is missing", () => { + const activateSpy = vi.fn(); + const { wrapper } = mountPhotoLabels({ + modelOverrides: { activateLabel: activateSpy }, + }); + + wrapper.vm.activateLabel(null); + + expect(activateSpy).not.toHaveBeenCalled(); + }); + + it("delegates to model.activateLabel for valid label", () => { + const activateSpy = vi.fn(); + const { wrapper } = mountPhotoLabels({ + modelOverrides: { activateLabel: activateSpy }, + }); + + const label = { ID: 7, Name: "Summer" }; + + wrapper.vm.activateLabel(label); + + expect(activateSpy).toHaveBeenCalledWith(7); + }); + }); + + describe("searchLabel", () => { + it("navigates to all route with label query and emits close", () => { + const push = vi.fn(() => Promise.resolve()); + const { wrapper, router } = mountPhotoLabels({ + routerOverrides: { push }, + }); + + const label = { Slug: "animals" }; + + wrapper.vm.searchLabel(label); + + expect(router.push).toHaveBeenCalledWith({ + name: "all", + query: { q: "label:animals" }, + }); + expect(wrapper.emitted("close")).toBeTruthy(); + }); + }); + + describe("openPhoto", () => { + it("opens photo in lightbox using Thumb.fromPhotos when model is present", () => { + const thumbModel = {}; + const fromPhotosSpy = vi.spyOn(Thumb, "fromPhotos").mockReturnValue([thumbModel]); + + const { wrapper, model, lightbox } = mountPhotoLabels(); + + wrapper.vm.openPhoto(); + + expect(fromPhotosSpy).toHaveBeenCalledWith([model]); + expect(lightbox.openModels).toHaveBeenCalledWith([thumbModel], 0); + }); + + it("does nothing when model is missing", () => { + const fromPhotosSpy = vi.spyOn(Thumb, "fromPhotos").mockReturnValue([]); + const { wrapper, lightbox } = mountPhotoLabels({ viewHasModel: false }); + + wrapper.vm.openPhoto(); + + expect(fromPhotosSpy).not.toHaveBeenCalled(); + expect(lightbox.openModels).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/frontend/tests/vitest/component/photo/toolbar.test.js b/frontend/tests/vitest/component/photo/toolbar.test.js new file mode 100644 index 000000000..d4f136d78 --- /dev/null +++ b/frontend/tests/vitest/component/photo/toolbar.test.js @@ -0,0 +1,346 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import { shallowMount, config as VTUConfig } from "@vue/test-utils"; +import PPhotoToolbar from "component/photo/toolbar.vue"; +import * as contexts from "options/contexts"; +import "../../fixtures"; + +function mountToolbar({ + context = contexts.Photos, + embedded = false, + filter = { + q: "", + country: "", + camera: 0, + year: 0, + month: 0, + color: "", + label: "", + order: "newest", + latlng: null, + }, + staticFilter = {}, + settings = { view: "mosaic" }, + featuresOverrides = {}, + searchOverrides = {}, + allowMock, + refresh = vi.fn(), + updateFilter = vi.fn(), + updateQuery = vi.fn(), + eventPublish, + routerOverrides = {}, + clipboard, + openUrlSpy, +} = {}) { + const baseConfig = VTUConfig.global.mocks.$config; + const baseSettings = baseConfig.getSettings ? baseConfig.getSettings() : { features: {} }; + + const features = { + ...(baseSettings.features || {}), + upload: true, + delete: true, + settings: true, + ...featuresOverrides, + }; + + const search = { + listView: true, + ...searchOverrides, + }; + + const configMock = { + ...baseConfig, + getSettings: vi.fn(() => ({ + ...baseSettings, + features, + search, + })), + allow: allowMock || vi.fn(() => true), + values: { + countries: [], + cameras: [], + categories: [], + ...(baseConfig.values || {}), + }, + }; + + const publish = eventPublish || vi.fn(); + + const router = { + push: vi.fn(), + resolve: vi.fn((route) => ({ + href: `/library/${route.name || "browse"}`, + })), + ...routerOverrides, + }; + + const clipboardMock = + clipboard || + { + clear: vi.fn(), + }; + + const baseUtil = VTUConfig.global.mocks.$util || {}; + const util = { + ...baseUtil, + openUrl: openUrlSpy || vi.fn(), + }; + + const wrapper = shallowMount(PPhotoToolbar, { + props: { + context, + filter, + staticFilter, + settings, + embedded, + refresh, + updateFilter, + updateQuery, + }, + global: { + mocks: { + $config: configMock, + $session: { isSuperAdmin: vi.fn(() => false) }, + $event: { + ...(VTUConfig.global.mocks.$event || {}), + publish, + }, + $router: router, + $clipboard: clipboardMock, + $util: util, + }, + stubs: { + PActionMenu: true, + PConfirmDialog: true, + }, + }, + }); + + return { + wrapper, + configMock, + publish, + router, + clipboard: clipboardMock, + refresh, + updateFilter, + updateQuery, + openUrl: util.openUrl, + }; +} + +describe("component/photo/toolbar", () => { + afterEach(() => { + vi.restoreAllMocks(); + }); + + describe("menuActions", () => { + it("shows upload and docs actions for photos context when upload is allowed", () => { + const { wrapper } = mountToolbar(); + + const actions = wrapper.vm.menuActions(); + const byName = (name) => actions.find((a) => a.name === name); + + const refreshAction = byName("refresh"); + const uploadAction = byName("upload"); + const docsAction = byName("docs"); + const troubleshootingAction = byName("troubleshooting"); + + expect(refreshAction).toBeDefined(); + expect(uploadAction).toBeDefined(); + expect(docsAction).toBeDefined(); + expect(troubleshootingAction).toBeDefined(); + + expect(refreshAction.visible).toBe(true); + expect(uploadAction.visible).toBe(true); + expect(docsAction.visible).toBe(true); + expect(troubleshootingAction.visible).toBe(false); + }); + + it("hides upload action in archive and hidden contexts", () => { + const { wrapper: archiveWrapper } = mountToolbar({ context: contexts.Archive }); + const archiveActions = archiveWrapper.vm.menuActions(); + const archiveUpload = archiveActions.find((a) => a.name === "upload"); + const archiveDocs = archiveActions.find((a) => a.name === "docs"); + const archiveTroubleshooting = archiveActions.find((a) => a.name === "troubleshooting"); + + expect(archiveUpload).toBeDefined(); + expect(archiveDocs).toBeDefined(); + expect(archiveTroubleshooting).toBeDefined(); + + expect(archiveUpload.visible).toBe(false); + expect(archiveDocs.visible).toBe(true); + expect(archiveTroubleshooting.visible).toBe(false); + + const { wrapper: hiddenWrapper } = mountToolbar({ context: contexts.Hidden }); + const hiddenActions = hiddenWrapper.vm.menuActions(); + const hiddenUpload = hiddenActions.find((a) => a.name === "upload"); + const hiddenDocs = hiddenActions.find((a) => a.name === "docs"); + const hiddenTroubleshooting = hiddenActions.find((a) => a.name === "troubleshooting"); + + expect(hiddenUpload).toBeDefined(); + expect(hiddenDocs).toBeDefined(); + expect(hiddenTroubleshooting).toBeDefined(); + + expect(hiddenUpload.visible).toBe(false); + expect(hiddenDocs.visible).toBe(false); + expect(hiddenTroubleshooting.visible).toBe(true); + }); + + it("invokes refresh prop and publishes upload dialog events on click", () => { + const refresh = vi.fn(); + const publish = vi.fn(); + const { wrapper } = mountToolbar({ refresh, eventPublish: publish }); + + const actions = wrapper.vm.menuActions(); + const refreshAction = actions.find((a) => a.name === "refresh"); + const uploadAction = actions.find((a) => a.name === "upload"); + + expect(refreshAction).toBeDefined(); + expect(uploadAction).toBeDefined(); + + refreshAction.click(); + expect(refresh).toHaveBeenCalledTimes(1); + + uploadAction.click(); + expect(publish).toHaveBeenCalledWith("dialog.upload"); + }); + }); + + describe("view handling", () => { + it("setView keeps list when listView search setting is enabled", () => { + const refresh = vi.fn(); + const { wrapper } = mountToolbar({ + refresh, + searchOverrides: { listView: true }, + }); + + wrapper.vm.expanded = true; + wrapper.vm.setView("list"); + + expect(refresh).toHaveBeenCalledWith({ view: "list" }); + expect(wrapper.vm.expanded).toBe(false); + }); + + it("setView falls back to mosaic when list view is disabled", () => { + const refresh = vi.fn(); + const { wrapper } = mountToolbar({ + refresh, + searchOverrides: { listView: false }, + }); + + wrapper.vm.expanded = true; + wrapper.vm.setView("list"); + + expect(refresh).toHaveBeenCalledWith({ view: "mosaic" }); + expect(wrapper.vm.expanded).toBe(false); + }); + }); + + describe("sortOptions", () => { + it("provides archive-specific sort options for archive context", () => { + const { wrapper } = mountToolbar({ context: contexts.Archive }); + + const values = wrapper.vm.sortOptions.map((o) => o.value); + expect(values).toContain("archived"); + expect(values).not.toContain("similar"); + expect(values).not.toContain("relevance"); + }); + + it("includes similarity and relevance options in default photos context", () => { + const { wrapper } = mountToolbar({ context: contexts.Photos }); + + const values = wrapper.vm.sortOptions.map((o) => o.value); + expect(values).toContain("similar"); + expect(values).toContain("relevance"); + }); + }); + + describe("delete actions", () => { + it("deleteAll opens confirmation dialog only when delete is allowed", () => { + const allowAll = vi.fn(() => true); + const { wrapper } = mountToolbar({ + allowMock: allowAll, + featuresOverrides: { delete: true }, + }); + + wrapper.vm.deleteAll(); + expect(wrapper.vm.dialog.delete).toBe(true); + + const denyDelete = vi.fn((resource, action) => { + if (resource === "photos" && action === "delete") { + return false; + } + return true; + }); + const { wrapper: noDeleteWrapper } = mountToolbar({ + allowMock: denyDelete, + featuresOverrides: { delete: true }, + }); + + noDeleteWrapper.vm.deleteAll(); + expect(noDeleteWrapper.vm.dialog.delete).toBe(false); + }); + + it("batchDelete posts delete request and clears clipboard on success", async () => { + const clipboard = { clear: vi.fn() }; + const { default: $notify } = await import("common/notify"); + const { default: $api } = await import("common/api"); + + const postSpy = vi.spyOn($api, "post").mockResolvedValue({ data: {} }); + const notifySpy = vi.spyOn($notify, "success"); + + const { wrapper } = mountToolbar({ + clipboard, + featuresOverrides: { delete: true }, + }); + + wrapper.vm.dialog.delete = true; + + await wrapper.vm.batchDelete(); + + expect(postSpy).toHaveBeenCalledWith("batch/photos/delete", { all: true }); + expect(wrapper.vm.dialog.delete).toBe(false); + expect(notifySpy).toHaveBeenCalledWith("Permanently deleted"); + expect(clipboard.clear).toHaveBeenCalledTimes(1); + }); + }); + + describe("browse actions", () => { + it("clearLocation navigates back to browse list", () => { + const push = vi.fn(); + const { wrapper } = mountToolbar({ + routerOverrides: { + push, + }, + }); + + wrapper.vm.clearLocation(); + expect(push).toHaveBeenCalledWith({ name: "browse" }); + }); + + it("onBrowse opens places browse in new tab on desktop", () => { + const push = vi.fn(); + const openUrlSpy = vi.fn(); + + const staticFilter = { q: "country:US" }; + const { wrapper, router, openUrl } = mountToolbar({ + staticFilter, + routerOverrides: { + push, + resolve: vi.fn((route) => ({ + href: `/library/${route.name}?q=${route.query?.q || ""}`, + })), + }, + openUrlSpy, + }); + + wrapper.vm.onBrowse(); + + expect(push).not.toHaveBeenCalled(); + expect(router.resolve).toHaveBeenCalledWith({ name: "places_browse", query: staticFilter }); + expect(openUrl).toHaveBeenCalledWith("/library/places_browse?q=country:US"); + }); + }); +}); + + diff --git a/frontend/tests/vitest/model/album.test.js b/frontend/tests/vitest/model/album.test.js index b27a252f8..a5481fba7 100644 --- a/frontend/tests/vitest/model/album.test.js +++ b/frontend/tests/vitest/model/album.test.js @@ -1,8 +1,18 @@ -import { describe, it, expect } from "vitest"; +import { describe, it, expect, beforeEach, afterEach } from "vitest"; import "../fixtures"; import { Album, BatchSize } from "model/album"; describe("model/album", () => { + let originalBatchSize; + + beforeEach(() => { + originalBatchSize = Album.batchSize(); + }); + + afterEach(() => { + Album.setBatchSize(originalBatchSize); + }); + it("should get route view", () => { const values = { ID: 5, Title: "Christmas 2019", Slug: "christmas-2019" }; const album = new Album(values); @@ -312,7 +322,6 @@ describe("model/album", () => { expect(Album.batchSize()).toBe(BatchSize); Album.setBatchSize(30); expect(Album.batchSize()).toBe(30); - Album.setBatchSize(BatchSize); }); it("should like album", () => { diff --git a/frontend/tests/vitest/model/face.test.js b/frontend/tests/vitest/model/face.test.js index 9f072f0d8..13c974c98 100644 --- a/frontend/tests/vitest/model/face.test.js +++ b/frontend/tests/vitest/model/face.test.js @@ -1,8 +1,18 @@ -import { describe, it, expect } from "vitest"; +import { describe, it, expect, beforeEach, afterEach } from "vitest"; import "../fixtures"; import { Face, BatchSize } from "model/face"; describe("model/face", () => { + let originalBatchSize; + + beforeEach(() => { + originalBatchSize = Face.batchSize(); + }); + + afterEach(() => { + Face.setBatchSize(originalBatchSize); + }); + it("should get face defaults", () => { const values = {}; const face = new Face(values); @@ -146,7 +156,6 @@ describe("model/face", () => { expect(Face.batchSize()).toBe(BatchSize); Face.setBatchSize(30); expect(Face.batchSize()).toBe(30); - Face.setBatchSize(BatchSize); }); it("should get collection resource", () => { diff --git a/frontend/tests/vitest/model/label.test.js b/frontend/tests/vitest/model/label.test.js index d13afe834..913f1dda1 100644 --- a/frontend/tests/vitest/model/label.test.js +++ b/frontend/tests/vitest/model/label.test.js @@ -1,8 +1,18 @@ -import { describe, it, expect } from "vitest"; +import { describe, it, expect, beforeEach, afterEach } from "vitest"; import "../fixtures"; import { Label, BatchSize } from "model/label"; describe("model/label", () => { + let originalBatchSize; + + beforeEach(() => { + originalBatchSize = Label.batchSize(); + }); + + afterEach(() => { + Label.setBatchSize(originalBatchSize); + }); + it("should get route view", () => { const values = { ID: 5, UID: "ABC123", Name: "Black Cat", Slug: "black-cat" }; const label = new Label(values); @@ -15,7 +25,6 @@ describe("model/label", () => { expect(Label.batchSize()).toBe(BatchSize); Label.setBatchSize(30); expect(Label.batchSize()).toBe(30); - Label.setBatchSize(BatchSize); }); it("should return classes", () => { diff --git a/frontend/tests/vitest/model/marker.test.js b/frontend/tests/vitest/model/marker.test.js index 52d065856..ae8d5a48c 100644 --- a/frontend/tests/vitest/model/marker.test.js +++ b/frontend/tests/vitest/model/marker.test.js @@ -1,8 +1,18 @@ -import { describe, it, expect } from "vitest"; +import { describe, it, expect, beforeEach, afterEach } from "vitest"; import "../fixtures"; import { Marker, BatchSize } from "model/marker"; describe("model/marker", () => { + let originalBatchSize; + + beforeEach(() => { + originalBatchSize = Marker.batchSize(); + }); + + afterEach(() => { + Marker.setBatchSize(originalBatchSize); + }); + it("should get marker defaults", () => { const values = { FileUID: "fghjojp" }; const marker = new Marker(values); @@ -193,7 +203,6 @@ describe("model/marker", () => { expect(Marker.batchSize()).toBe(BatchSize); Marker.setBatchSize(30); expect(Marker.batchSize()).toBe(30); - Marker.setBatchSize(BatchSize); }); it("should get collection resource", () => { diff --git a/frontend/tests/vitest/model/photo.test.js b/frontend/tests/vitest/model/photo.test.js index f610d0bdc..d93615a40 100644 --- a/frontend/tests/vitest/model/photo.test.js +++ b/frontend/tests/vitest/model/photo.test.js @@ -344,21 +344,21 @@ describe("model/photo", () => { expect(result5).toBe("July 2012"); }); - it("should test whether photo has location", () => { + it("should report hasLocation true for non-zero coordinates", () => { const values = { ID: 5, Title: "Crazy Cat", Lat: 36.442881666666665, Lng: 28.229493333333334 }; const photo = new Photo(values); const result = photo.hasLocation(); expect(result).toBe(true); }); - it("should test whether photo has location", () => { + it("should report hasLocation false for zero coordinates", () => { const values = { ID: 5, Title: "Crazy Cat", Lat: 0, Lng: 0 }; const photo = new Photo(values); const result = photo.hasLocation(); expect(result).toBe(false); }); - it("should get location", () => { + it("should get primary location label with country", () => { const values = { ID: 5, Title: "Crazy Cat", @@ -372,7 +372,7 @@ describe("model/photo", () => { expect(result).toBe("Cape Point, South Africa"); }); - it("should get location", () => { + it("should get full location with state and country", () => { const values = { ID: 5, Title: "Crazy Cat", @@ -389,7 +389,7 @@ describe("model/photo", () => { expect(result).toBe("Cape Point, State, South Africa"); }); - it("should get location", () => { + it("should return Unknown when country name does not match", () => { const values = { ID: 5, Title: "Crazy Cat", @@ -405,14 +405,14 @@ describe("model/photo", () => { expect(result).toBe("Unknown"); }); - it("should get location", () => { + it("should return Unknown when only country name is set", () => { const values = { ID: 5, Title: "Crazy Cat", CountryName: "Africa", PlaceCity: "Cape Town" }; const photo = new Photo(values); const result = photo.locationInfo(); expect(result).toBe("Unknown"); }); - it("should get camera", () => { + it("should get camera from model and file camera data", () => { const values = { ID: 5, Title: "Crazy Cat", CameraModel: "EOSD10", CameraMake: "Canon" }; const photo = new Photo(values); const result = photo.getCamera(); @@ -438,7 +438,7 @@ describe("model/photo", () => { expect(photo2.getCamera()).toBe("Canon abc"); }); - it("should get camera", () => { + it("should return Unknown when camera info is missing", () => { const values = { ID: 5, Title: "Crazy Cat" }; const photo = new Photo(values); const result = photo.getCamera(); diff --git a/frontend/tests/vitest/model/subject.test.js b/frontend/tests/vitest/model/subject.test.js index 6d8a0055e..6c7a0d305 100644 --- a/frontend/tests/vitest/model/subject.test.js +++ b/frontend/tests/vitest/model/subject.test.js @@ -1,8 +1,18 @@ -import { describe, it, expect } from "vitest"; +import { describe, it, expect, beforeEach, afterEach } from "vitest"; import "../fixtures"; import { Subject, BatchSize } from "model/subject"; describe("model/subject", () => { + let originalBatchSize; + + beforeEach(() => { + originalBatchSize = Subject.batchSize(); + }); + + afterEach(() => { + Subject.setBatchSize(originalBatchSize); + }); + it("should get face defaults", () => { const values = {}; const subject = new Subject(values); @@ -238,7 +248,6 @@ describe("model/subject", () => { expect(Subject.batchSize()).toBe(BatchSize); Subject.setBatchSize(30); expect(Subject.batchSize()).toBe(30); - Subject.setBatchSize(BatchSize); }); it("should get collection resource", () => { diff --git a/frontend/tests/vitest/options/options.test.js b/frontend/tests/vitest/options/options.test.js index 6a9425f00..dbc279b8d 100644 --- a/frontend/tests/vitest/options/options.test.js +++ b/frontend/tests/vitest/options/options.test.js @@ -1,4 +1,4 @@ -import { describe, it, expect } from "vitest"; +import { describe, it, expect, beforeEach, afterEach } from "vitest"; import "../fixtures"; import * as options from "options/options"; import { @@ -25,6 +25,15 @@ import { } from "options/options"; describe("options/options", () => { + let originalDefaultLocale; + + beforeEach(() => { + originalDefaultLocale = options.DefaultLocale; + }); + + afterEach(() => { + SetDefaultLocale(originalDefaultLocale); + }); it("should get timezones", () => { const timezones = options.TimeZones(); expect(timezones[0].ID).toBe("Local"); @@ -93,13 +102,10 @@ describe("options/options", () => { }); it("should set default locale", () => { - // Assuming DefaultLocale is exported and mutable for testing purposes - // Initial state check might depend on test execution order, so we control it here. - SetDefaultLocale("en"); // Ensure starting state + SetDefaultLocale("en"); expect(options.DefaultLocale).toBe("en"); SetDefaultLocale("de"); expect(options.DefaultLocale).toBe("de"); - SetDefaultLocale("en"); // Reset for other tests }); it("should return default when no locale is provided", () => { diff --git a/frontend/tests/vitest/setup.js b/frontend/tests/vitest/setup.js index a64a2e0a5..3a0e9854b 100644 --- a/frontend/tests/vitest/setup.js +++ b/frontend/tests/vitest/setup.js @@ -30,9 +30,9 @@ if (typeof global.ResizeObserver === "undefined") { constructor(callback) { this.callback = callback; } - observe() {} - unobserve() {} - disconnect() {} + observe() { } + unobserve() { } + disconnect() { } }; } @@ -54,22 +54,22 @@ config.global.mocks = { $event: { subscribe: () => "sub-id", subscribeOnce: () => "sub-id-once", - unsubscribe: () => {}, - publish: () => {}, + unsubscribe: () => { }, + publish: () => { }, }, $view: { - enter: () => {}, - leave: () => {}, + enter: () => { }, + leave: () => { }, isActive: () => true, }, - $notify: { success: () => {}, error: () => {}, warn: () => {} }, + $notify: { success: vi.fn(), error: vi.fn(), warn: vi.fn(), info: vi.fn() }, $fullscreen: { isSupported: () => true, isEnabled: () => false, request: () => Promise.resolve(), exit: () => Promise.resolve(), }, - $clipboard: { selection: [], has: () => false, toggle: () => {} }, + $clipboard: { selection: [], has: () => false, toggle: () => { } }, $util: { hasTouch: () => false, encodeHTML: (s) => s, diff --git a/frontend/webpack.config.js b/frontend/webpack.config.js index 57bd16b6c..1361ed412 100644 --- a/frontend/webpack.config.js +++ b/frontend/webpack.config.js @@ -78,7 +78,9 @@ const config = { clean: true, }, resolve: { - modules: isCustom ? [PATHS.custom, PATHS.src, PATHS.modules] : [PATHS.src, PATHS.modules], + modules: isCustom + ? [PATHS.custom, PATHS.src, "node_modules", PATHS.modules] + : [PATHS.src, "node_modules", PATHS.modules], preferRelative: true, alias: { "vue$": "vue/dist/vue.runtime.esm-bundler.js", diff --git a/go.mod b/go.mod index d2d622e4d..91cc84be3 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/esimov/pigo v1.4.6 github.com/gin-contrib/gzip v1.2.5 github.com/gin-gonic/gin v1.11.0 - github.com/golang/geo v0.0.0-20251209161508-25c597310d4b + github.com/golang/geo v0.0.0-20260120070133-792bb8583fbb github.com/google/open-location-code/go v0.0.0-20250620134813-83986da0156b github.com/gorilla/websocket v1.5.3 github.com/gosimple/slug v1.15.0 @@ -33,29 +33,29 @@ require ( github.com/paulmach/go.geojson v1.5.0 github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 github.com/sevlyar/go-daemon v0.1.6 - github.com/sirupsen/logrus v1.9.3 + github.com/sirupsen/logrus v1.9.4 github.com/stretchr/testify v1.11.1 github.com/tidwall/gjson v1.18.0 github.com/ulule/deepcopier v0.0.0-20200430083143-45decc6639b6 go4.org v0.0.0-20230225012048-214862532bf5 // indirect - golang.org/x/crypto v0.46.0 - golang.org/x/net v0.48.0 - gonum.org/v1/gonum v0.16.0 + golang.org/x/crypto v0.47.0 + golang.org/x/net v0.49.0 + gonum.org/v1/gonum v0.17.0 gopkg.in/yaml.v2 v2.4.0 ) require ( github.com/go-xmlfmt/xmlfmt v1.1.3 // indirect - golang.org/x/image v0.34.0 + golang.org/x/image v0.35.0 ) -require github.com/olekukonko/tablewriter v1.1.2 +require github.com/olekukonko/tablewriter v1.1.3 require github.com/google/uuid v1.6.0 require github.com/chzyer/readline v1.5.1 // indirect -require github.com/gabriel-vasile/mimetype v1.4.11 +require github.com/gabriel-vasile/mimetype v1.4.12 require ( golang.org/x/sync v0.19.0 @@ -66,17 +66,17 @@ require github.com/go-ldap/ldap/v3 v3.4.12 require ( github.com/prometheus/client_golang v1.23.2 - github.com/prometheus/common v0.67.4 + github.com/prometheus/common v0.67.5 ) require github.com/dustinkirkland/golang-petname v0.0.0-20240428194347-eebcea082ee0 -require golang.org/x/text v0.32.0 +require golang.org/x/text v0.33.0 require ( github.com/IGLOU-EU/go-wildcard v1.0.3 github.com/davidbyttow/govips/v2 v2.16.0 - github.com/go-co-op/gocron/v2 v2.18.2 + github.com/go-co-op/gocron/v2 v2.19.0 github.com/go-sql-driver/mysql v1.9.3 github.com/golang-jwt/jwt/v5 v5.3.0 github.com/pquerna/otp v1.5.0 @@ -84,13 +84,13 @@ require ( github.com/robfig/cron/v3 v3.0.1 github.com/swaggo/files v1.0.1 github.com/swaggo/gin-swagger v1.6.1 - github.com/ugjka/go-tz/v2 v2.2.6 + github.com/ugjka/go-tz/v2 v2.2.7 github.com/urfave/cli/v2 v2.27.7 github.com/wamuir/graft v0.10.0 - github.com/yalue/onnxruntime_go v1.24.0 - github.com/zitadel/oidc/v3 v3.45.1 - golang.org/x/mod v0.31.0 - golang.org/x/sys v0.39.0 + github.com/yalue/onnxruntime_go v1.25.0 + github.com/zitadel/oidc/v3 v3.45.3 + golang.org/x/mod v0.32.0 + golang.org/x/sys v0.40.0 google.golang.org/protobuf v1.36.11 gorm.io/driver/mysql v1.5.7 gorm.io/driver/postgres v1.5.9 @@ -106,9 +106,9 @@ require ( github.com/bytedance/gopkg v0.1.3 // indirect github.com/bytedance/sonic/loader v0.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/clipperhouse/displaywidth v0.6.0 // indirect + github.com/clipperhouse/displaywidth v0.7.0 // indirect github.com/clipperhouse/stringish v0.1.1 // indirect - github.com/clipperhouse/uax29/v2 v2.3.0 // indirect + github.com/clipperhouse/uax29/v2 v2.3.1 // indirect github.com/cloudwego/base64x v0.1.6 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect github.com/davecgh/go-spew v1.1.1 // indirect @@ -160,8 +160,8 @@ require ( github.com/muhlemmer/gu v0.3.1 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect - github.com/olekukonko/errors v1.1.0 // indirect - github.com/olekukonko/ll v0.1.3 // indirect + github.com/olekukonko/errors v1.2.0 // indirect + github.com/olekukonko/ll v0.1.4-0.20260115111900-9e59c2286df0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/procfs v0.17.0 // indirect github.com/quic-go/qpack v0.5.1 // indirect @@ -178,14 +178,14 @@ require ( require ( github.com/mattn/go-runewidth v0.0.19 // indirect github.com/tidwall/pretty v1.2.1 // indirect - github.com/zitadel/schema v1.3.1 // indirect + github.com/zitadel/schema v1.3.2 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/otel v1.38.0 // indirect - go.opentelemetry.io/otel/metric v1.38.0 // indirect - go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/otel v1.39.0 // indirect + go.opentelemetry.io/otel/metric v1.39.0 // indirect + go.opentelemetry.io/otel/trace v1.39.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect - golang.org/x/oauth2 v0.33.0 // indirect - golang.org/x/tools v0.39.0 // indirect + golang.org/x/oauth2 v0.34.0 // indirect + golang.org/x/tools v0.40.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index edfe3855a..69c782da4 100644 --- a/go.sum +++ b/go.sum @@ -27,8 +27,8 @@ github.com/abema/go-mp4 v1.4.1 h1:YoS4VRqd+pAmddRPLFf8vMk74kuGl6ULSjzhsIqwr6M= github.com/abema/go-mp4 v1.4.1/go.mod h1:vPl9t5ZK7K0x68jh12/+ECWBCXoWuIDtNgPtU2f04ws= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bmatcuk/doublestar/v4 v4.9.1 h1:X8jg9rRZmJd4yRy7ZeNDRnM+T3ZfHv15JiBJ/avrEXE= -github.com/bmatcuk/doublestar/v4 v4.9.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= +github.com/bmatcuk/doublestar/v4 v4.9.2 h1:b0mc6WyRSYLjzofB2v/0cuDUZ+MqoGyH3r0dVij35GI= +github.com/bmatcuk/doublestar/v4 v4.9.2/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.1.0 h1:ChaYjBR63fr4LFyGn8E8nt7dBSt3MiU3zMOZqFvVkHo= github.com/boombuler/barcode v1.1.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= @@ -51,12 +51,12 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/clipperhouse/displaywidth v0.6.0 h1:k32vueaksef9WIKCNcoqRNyKbyvkvkysNYnAWz2fN4s= -github.com/clipperhouse/displaywidth v0.6.0/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o= +github.com/clipperhouse/displaywidth v0.7.0 h1:QNv1GYsnLX9QBrcWUtMlogpTXuM5FVnBwKWp1O5NwmE= +github.com/clipperhouse/displaywidth v0.7.0/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o= github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs= github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA= -github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4= -github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= +github.com/clipperhouse/uax29/v2 v2.3.1 h1:RjM8gnVbFbgI67SBekIC7ihFpyXwRPYWXn9BZActHbw= +github.com/clipperhouse/uax29/v2 v2.3.1/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo= @@ -119,8 +119,8 @@ github.com/esimov/pigo v1.4.6/go.mod h1:uqj9Y3+3IRYhFK071rxz1QYq0ePhA6+R9jrUZavi github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= -github.com/gabriel-vasile/mimetype v1.4.11 h1:AQvxbp830wPhHTqc1u7nzoLT+ZFxGY7emj5DR5DYFik= -github.com/gabriel-vasile/mimetype v1.4.11/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= +github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gin-contrib/gzip v1.2.5 h1:fIZs0S+l17pIu1P5XRJOo/YNqfIuPCrZZ3TWB7pjckI= github.com/gin-contrib/gzip v1.2.5/go.mod h1:aomRgR7ftdZV3uWY0gW/m8rChfxau0n8YVvwlOHONzw= github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= @@ -129,8 +129,8 @@ github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls= github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE= github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= -github.com/go-co-op/gocron/v2 v2.18.2 h1:+5VU41FUXPWSPKLXZQ/77SGzUiPCcakU0v7ENc2H20Q= -github.com/go-co-op/gocron/v2 v2.18.2/go.mod h1:Zii6he+Zfgy5W9B+JKk/KwejFOW0kZTFvHtwIpR4aBI= +github.com/go-co-op/gocron/v2 v2.19.0 h1:OKf2y6LXPs/BgBI2fl8PxUpNAI1DA9Mg+hSeGOS38OU= +github.com/go-co-op/gocron/v2 v2.19.0/go.mod h1:5lEiCKk1oVJV39Zg7/YG10OnaVrDAV5GGR6O0663k6U= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs= github.com/go-errors/errors v1.1.1/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs= @@ -200,8 +200,8 @@ github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGw github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= github.com/golang/geo v0.0.0-20200319012246-673a6f80352d/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= -github.com/golang/geo v0.0.0-20251209161508-25c597310d4b h1:6y9D6yfaR5FyqoNoV2S+XJyhzeMUlkdIeUX1Ssj0FJQ= -github.com/golang/geo v0.0.0-20251209161508-25c597310d4b/go.mod h1:Mymr9kRGDc64JPr03TSZmuIBODZ3KyswLzm1xL0HFA8= +github.com/golang/geo v0.0.0-20260120070133-792bb8583fbb h1:OhyJ/wXEqRssvvFcXP8Wzoyn0fbiqWgjHXLKj3ZOU+4= +github.com/golang/geo v0.0.0-20260120070133-792bb8583fbb/go.mod h1:Mymr9kRGDc64JPr03TSZmuIBODZ3KyswLzm1xL0HFA8= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -329,12 +329,12 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc= github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6/go.mod h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0= -github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM= -github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y= -github.com/olekukonko/ll v0.1.3 h1:sV2jrhQGq5B3W0nENUISCR6azIPf7UBUpVq0x/y70Fg= -github.com/olekukonko/ll v0.1.3/go.mod h1:b52bVQRRPObe+yyBl0TxNfhesL0nedD4Cht0/zx55Ew= -github.com/olekukonko/tablewriter v1.1.2 h1:L2kI1Y5tZBct/O/TyZK1zIE9GlBj/TVs+AY5tZDCDSc= -github.com/olekukonko/tablewriter v1.1.2/go.mod h1:z7SYPugVqGVavWoA2sGsFIoOVNmEHxUAAMrhXONtfkg= +github.com/olekukonko/errors v1.2.0 h1:10Zcn4GeV59t/EGqJc8fUjtFT/FuUh5bTMzZ1XwmCRo= +github.com/olekukonko/errors v1.2.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y= +github.com/olekukonko/ll v0.1.4-0.20260115111900-9e59c2286df0 h1:jrYnow5+hy3WRDCBypUFvVKNSPPCdqgSXIE9eJDD8LM= +github.com/olekukonko/ll v0.1.4-0.20260115111900-9e59c2286df0/go.mod h1:b52bVQRRPObe+yyBl0TxNfhesL0nedD4Cht0/zx55Ew= +github.com/olekukonko/tablewriter v1.1.3 h1:VSHhghXxrP0JHl+0NnKid7WoEmd9/urKRJLysb70nnA= +github.com/olekukonko/tablewriter v1.1.3/go.mod h1:9VU0knjhmMkXjnMKrZ3+L2JhhtsQ/L38BbL3CRNE8tM= github.com/orcaman/writerseeker v0.0.0-20200621085525-1d3f536ff85e h1:s2RNOM/IGdY0Y6qfTeUKhDawdHDpK9RGBdx80qN4Ttw= github.com/orcaman/writerseeker v0.0.0-20200621085525-1d3f536ff85e/go.mod h1:nBdnFKj15wFbf94Rwfq4m30eAcyY9V/IyKAGQFtqkW0= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= @@ -354,8 +354,8 @@ github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UH github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc= -github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= +github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4= +github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw= github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= @@ -375,15 +375,14 @@ github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd h1:CmH9+J6ZSsIjUK github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= github.com/sevlyar/go-daemon v0.1.6 h1:EUh1MDjEM4BI109Jign0EaknA2izkOyi0LV3ro3QQGs= github.com/sevlyar/go-daemon v0.1.6/go.mod h1:6dJpPatBT9eUwM5VCw9Bt6CdX9Tk6UWvhW3MebLDRKE= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= +github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= @@ -409,8 +408,8 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugjka/go-tz/v2 v2.2.6 h1:xAjw0dwSoLZYVBv1lA+n165ibSnDtHguBQNbeAMDwNE= -github.com/ugjka/go-tz/v2 v2.2.6/go.mod h1:Jh35OKbERtwjZLWDZ2KgjD+bm5hb9Lx8nVD9Mv9NVzs= +github.com/ugjka/go-tz/v2 v2.2.7 h1:ibg9vEPF0NUd7nd1fzxguVaSBkYQ5qWQPPEopDZvKlc= +github.com/ugjka/go-tz/v2 v2.2.7/go.mod h1:Jh35OKbERtwjZLWDZ2KgjD+bm5hb9Lx8nVD9Mv9NVzs= github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= github.com/ulule/deepcopier v0.0.0-20200430083143-45decc6639b6 h1:TtyC78WMafNW8QFfv3TeP3yWNDG+uxNkk9vOrnDu6JA= @@ -421,27 +420,27 @@ github.com/wamuir/graft v0.10.0 h1:HSpBUvm7O+jwsRIuDQlw80xW4xMXRFkOiVLtWaZCU2s= github.com/wamuir/graft v0.10.0/go.mod h1:k6NJX3fCM/xzh5NtHky9USdgHTcz2vAvHp4c23I6UK4= github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342 h1:FnBeRrxr7OU4VvAzt5X7s6266i6cSVkkFPS0TuXWbIg= github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= -github.com/yalue/onnxruntime_go v1.24.0 h1:IdgJLxxyotlsUTmL1UnHZgBzXJGgY51LZ4vQ5rZeOXU= -github.com/yalue/onnxruntime_go v1.24.0/go.mod h1:b4X26A8pekNb1ACJ58wAXgNKeUCGEAQ9dmACut9Sm/4= +github.com/yalue/onnxruntime_go v1.25.0 h1:nlhVau1BpLZ/BYr+WpPZCJRD/WES0qo6dK7aKyyAs3g= +github.com/yalue/onnxruntime_go v1.25.0/go.mod h1:b4X26A8pekNb1ACJ58wAXgNKeUCGEAQ9dmACut9Sm/4= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zitadel/logging v0.6.2 h1:MW2kDDR0ieQynPZ0KIZPrh9ote2WkxfBif5QoARDQcU= github.com/zitadel/logging v0.6.2/go.mod h1:z6VWLWUkJpnNVDSLzrPSQSQyttysKZ6bCRongw0ROK4= -github.com/zitadel/oidc/v3 v3.45.1 h1:x7J8NywTUtLR9T5uu2dufae3gJrl6VVpIfvGZy+kzJg= -github.com/zitadel/oidc/v3 v3.45.1/go.mod h1:oFArtAPTXEA4ajkIe/JfBjv7hhlD0kr///UqaO3Uzd0= -github.com/zitadel/schema v1.3.1 h1:QT3kwiRIRXXLVAs6gCK/u044WmUVh6IlbLXUsn6yRQU= -github.com/zitadel/schema v1.3.1/go.mod h1:071u7D2LQacy1HAN+YnMd/mx1qVE2isb0Mjeqg46xnU= +github.com/zitadel/oidc/v3 v3.45.3 h1:iaicqH5M7L5a973DTaG9UVSE14Z6Nj6hN41pp7kYBIw= +github.com/zitadel/oidc/v3 v3.45.3/go.mod h1:yerW4/1YA5rUgjSjHsJ4HRnMbaKsyeIJkzyQrwDQ4t8= +github.com/zitadel/schema v1.3.2 h1:gfJvt7dOMfTmxzhscZ9KkapKo3Nei3B6cAxjav+lyjI= +github.com/zitadel/schema v1.3.2/go.mod h1:IZmdfF9Wu62Zu6tJJTH3UsArevs3Y4smfJIj3L8fzxw= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= @@ -462,8 +461,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= -golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= -golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -478,8 +477,8 @@ golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+o golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20220902085622-e7cb96979f69/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY= golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= -golang.org/x/image v0.34.0 h1:33gCkyw9hmwbZJeZkct8XyR11yH889EQt/QH4VmXMn8= -golang.org/x/image v0.34.0/go.mod h1:2RNFBZRB+vnwwFil8GkMdRvrJOFd1AzdZI6vOY+eJVU= +golang.org/x/image v0.35.0 h1:LKjiHdgMtO8z7Fh18nGY6KDcoEtVfsgLDPeLyguqb7I= +golang.org/x/image v0.35.0/go.mod h1:MwPLTVgvxSASsxdLzKrl8BRFuyqMyGhLwmC+TO1Sybk= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -500,8 +499,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= -golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= +golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= +golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -530,15 +529,15 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= -golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= -golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= -golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -575,7 +574,6 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -585,8 +583,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= -golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20191110171634-ad39bd3f0407/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -608,8 +606,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= -golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= -golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= @@ -642,13 +640,13 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= -golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= +golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= +golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= -gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= diff --git a/internal/ai/classify/README.md b/internal/ai/classify/README.md new file mode 100644 index 000000000..a0da292e3 --- /dev/null +++ b/internal/ai/classify/README.md @@ -0,0 +1,31 @@ +## PhotoPrism — Classification Package + +**Last Updated:** December 23, 2025 + +### Overview + +`internal/ai/classify` wraps PhotoPrism’s TensorFlow-based image classification (labels). It loads SavedModel classifiers (Nasnet by default), prepares inputs, runs inference, and maps output probabilities to label rules. + +### How It Works + +- **Model Loading** — The classifier loads a SavedModel under `assets/models/` and resolves model tags and input/output ops (see `vision.yml` overrides for custom models). +- **Input Preparation** — JPEGs are decoded and resized/cropped to the model’s expected input resolution. +- **Inference** — The model outputs probabilities; `Rules` apply thresholds and priority to produce final labels. + +### Memory & Performance + +TensorFlow tensors allocate C memory and are freed by Go GC finalizers. To keep RSS bounded during long runs, PhotoPrism periodically triggers garbage collection to return freed tensor memory to the OS. Tune with: + +- `PHOTOPRISM_TF_GC_EVERY` (default **200**, `0` disables). + Lower values reduce peak RSS but increase GC overhead and can slow indexing. + +### Troubleshooting Tips + +- **Labels are empty:** Verify the model labels file and that `Rules` thresholds are not too strict. +- **Model load failures:** Ensure `saved_model.pb` and `variables/` exist under the configured model path. +- **Unexpected outputs:** Check `TensorFlow.Input/Output` settings in `vision.yml` for custom models. + +### Related Docs + +- [`internal/ai/vision/README.md`](../vision/README.md) — model registry and `vision.yml` configuration +- [`internal/ai/tensorflow/README.md`](../tensorflow/README.md) — TensorFlow helpers, GC behavior, and model loading diff --git a/internal/ai/classify/model.go b/internal/ai/classify/model.go index 2cc242ed1..3410a17be 100644 --- a/internal/ai/classify/model.go +++ b/internal/ai/classify/model.go @@ -133,6 +133,8 @@ func (m *Model) Run(img []byte, confidenceThreshold int) (result Labels, err err return nil, loadErr } + defer tensorflow.MaybeCollectTensorMemory() + // Create input tensor from image. tensor, err := m.createTensor(img) diff --git a/internal/ai/face/README.md b/internal/ai/face/README.md index 6e23b28e7..e7154c41f 100644 --- a/internal/ai/face/README.md +++ b/internal/ai/face/README.md @@ -1,6 +1,6 @@ ## Face Detection and Embedding Guidelines -**Last Updated:** October 10, 2025 +**Last Updated:** December 23, 2025 ### Overview @@ -46,6 +46,10 @@ Runtime selection lives in `Config.FaceEngine()`; `auto` resolves to ONNX when t ### Embedding Handling +#### Memory Management + +FaceNet embeddings are generated through TensorFlow bindings that allocate tensors in C memory. Those allocations are released by Go GC finalizers, so long-running indexing jobs can show steadily rising RSS even when the Go heap stays small. To keep memory bounded during extended face indexing runs, PhotoPrism now triggers periodic garbage collection and returns freed C-allocated tensor buffers to the OS. You can tune this behavior with `PHOTOPRISM_TF_GC_EVERY` (default **200**; set to `0` to disable). Lower values reduce peak RSS but increase GC overhead and can slow indexing, so keep the default unless memory pressure is severe. + #### Normalization All embeddings, regardless of origin, are normalized to unit length (‖x‖₂ = 1): diff --git a/internal/ai/face/model.go b/internal/ai/face/model.go index 59591ed80..adfbfa7d7 100644 --- a/internal/ai/face/model.go +++ b/internal/ai/face/model.go @@ -129,6 +129,8 @@ func (m *Model) loadModel() error { // Run returns the face embeddings for an image. func (m *Model) Run(img image.Image) Embeddings { + defer tensorflow.MaybeCollectTensorMemory() + // Create input tensor from image. tensor, err := imageToTensor(img, m.resolution) diff --git a/internal/ai/nsfw/README.md b/internal/ai/nsfw/README.md new file mode 100644 index 000000000..cb35e7cdf --- /dev/null +++ b/internal/ai/nsfw/README.md @@ -0,0 +1,31 @@ +## PhotoPrism — NSFW Package + +**Last Updated:** December 23, 2025 + +### Overview + +`internal/ai/nsfw` runs the built-in TensorFlow NSFW classifier to score images for drawing, hentai, neutral, porn, and sexy content. It is used during indexing and metadata workflows when the NSFW model is enabled. + +### How It Works + +- **Model Loading** — Loads the NSFW SavedModel from `assets/models/` and resolves input/output ops (inferred if missing). +- **Input Preparation** — JPEG images are decoded and transformed to the configured input resolution. +- **Inference & Output** — Produces five class probabilities mapped into a `Result` struct for downstream thresholds and UI badges. + +### Memory & Performance + +TensorFlow tensors allocate C memory and are freed by Go GC finalizers. To keep RSS bounded during long runs, PhotoPrism periodically triggers garbage collection to return freed tensor memory to the OS. Tune with: + +- `PHOTOPRISM_TF_GC_EVERY` (default **200**, `0` disables). + Lower values reduce peak RSS but increase GC overhead and can slow indexing. + +### Troubleshooting Tips + +- **Model fails to load:** Verify `saved_model.pb` and `variables/` exist under the model path. +- **Unexpected scores:** Confirm the input resolution matches the model and that logits are handled correctly. +- **High memory usage:** Adjust `PHOTOPRISM_TF_GC_EVERY` or reduce concurrent indexing load. + +### Related Docs + +- [`internal/ai/vision/README.md`](../vision/README.md) — model registry and run scheduling +- [`internal/ai/tensorflow/README.md`](../tensorflow/README.md) — TensorFlow helpers, GC behavior, and model loading diff --git a/internal/ai/nsfw/model.go b/internal/ai/nsfw/model.go index 19d1af5ce..1c760f3f5 100644 --- a/internal/ai/nsfw/model.go +++ b/internal/ai/nsfw/model.go @@ -75,6 +75,8 @@ func (m *Model) Run(img []byte) (result Result, err error) { return result, loadErr } + defer tensorflow.MaybeCollectTensorMemory() + // Create input tensor from image. input, err := tensorflow.ImageTransform( img, fs.ImageJpeg, m.meta.Input.Resolution()) diff --git a/internal/ai/tensorflow/README.md b/internal/ai/tensorflow/README.md new file mode 100644 index 000000000..adc6c3ec0 --- /dev/null +++ b/internal/ai/tensorflow/README.md @@ -0,0 +1,41 @@ +## PhotoPrism — TensorFlow Package + +**Last Updated:** December 23, 2025 + +### Overview + +`internal/ai/tensorflow` provides the shared TensorFlow helpers used by PhotoPrism’s built-in AI features (labels, NSFW, and FaceNet embeddings). It wraps SavedModel loading, input/output discovery, image tensor preparation, and label handling so higher-level packages can focus on domain logic. + +### Key Components + +- **Model Loading** — `SavedModel`, `GetModelTagsInfo`, and `GetInputAndOutputFromSavedModel` discover and load SavedModel graphs with appropriate tags. +- **Input Preparation** — `Image`, `ImageTransform`, and `ImageTensorBuilder` convert JPEG images to tensors with the configured resolution, color order, and resize strategy. +- **Output Handling** — `AddSoftmax` can insert a softmax op when a model exports logits. +- **Labels** — `LoadLabels` loads label lists for classification models. + +### Model Loading Notes + +- Built-in models live under `assets/models/` and are accessed via helpers in `internal/ai/vision` and `internal/ai/classify`. +- When a model lacks explicit tags or signatures, the helpers attempt to infer input/output operations. Logs will show when inference kicks in. +- Classification models may emit logits; if `ModelInfo.Output.Logits` is true, a softmax op is injected at load time. + +### Memory & Garbage Collection + +TensorFlow tensors are allocated in C memory and freed by Go GC finalizers in the TensorFlow bindings. Long-running inference can therefore show increasing RSS even when the Go heap is small. PhotoPrism periodically triggers garbage collection to return freed C-allocated tensor buffers to the OS. Control this behavior with: + +- `PHOTOPRISM_TF_GC_EVERY` (default **200**, `0` disables). + Lower values reduce peak RSS but increase GC overhead and can slow indexing. + +### Troubleshooting Tips + +- **Model fails to load:** Verify the SavedModel path, tags, and that `saved_model.pb` plus `variables/` exist under `assets/models/`. +- **Input/output mismatch:** Check logs for inferred inputs/outputs and confirm `vision.yml` overrides (name, resolution, and `TensorFlow.Input/Output`). +- **Unexpected probabilities:** Ensure logits are handled correctly and labels match output indices. +- **High memory usage:** Confirm `PHOTOPRISM_TF_GC_EVERY` is set appropriately; model weights remain resident for the life of the process by design. + +### Related Docs + +- [`internal/ai/vision/README.md`](../vision/README.md) — model registry, `vision.yml` configuration, and run scheduling +- [`internal/ai/face/README.md`](../face/README.md) — FaceNet embeddings and face-specific tuning +- [`internal/ai/classify/README.md`](../classify/README.md) — classification workflow using TensorFlow helpers +- [`internal/ai/nsfw/README.md`](../nsfw/README.md) — NSFW model usage and result mapping diff --git a/internal/ai/tensorflow/gc.go b/internal/ai/tensorflow/gc.go new file mode 100644 index 000000000..6dc0a9300 --- /dev/null +++ b/internal/ai/tensorflow/gc.go @@ -0,0 +1,43 @@ +package tensorflow + +import ( + "os" + "runtime/debug" + "strconv" + "strings" + "sync/atomic" +) + +const gcEveryDefault uint64 = 200 + +var ( + gcEvery = gcEveryDefault + gcCounter uint64 +) + +func init() { + if v := strings.TrimSpace(os.Getenv("PHOTOPRISM_TF_GC_EVERY")); v != "" { + if strings.HasPrefix(v, "-") { + gcEvery = 0 + return + } + + if n, err := strconv.ParseUint(v, 10, 64); err == nil { + gcEvery = n + } + } +} + +// MaybeCollectTensorMemory triggers GC and returns freed C-allocated tensor memory +// to the OS every gcEvery calls; set gcEvery to 0 to disable the throttling. +func MaybeCollectTensorMemory() { + if gcEvery == 0 { + return + } + + if atomic.AddUint64(&gcCounter, 1)%gcEvery != 0 { + return + } + + debug.FreeOSMemory() +} diff --git a/internal/ai/vision/README.md b/internal/ai/vision/README.md index 818962860..a322627ae 100644 --- a/internal/ai/vision/README.md +++ b/internal/ai/vision/README.md @@ -1,12 +1,12 @@ ## PhotoPrism — Vision Package -**Last Updated:** December 10, 2025 +**Last Updated:** December 23, 2025 ### Overview `internal/ai/vision` provides the shared model registry, request builders, and parsers that power PhotoPrism’s caption, label, face, NSFW, and future generate workflows. It reads `vision.yml`, normalizes models, and dispatches calls to one of three engines: -- **TensorFlow (built‑in)** — default Nasnet / NSFW / Facenet models, no remote service required. +- **TensorFlow (built‑in)** — default Nasnet / NSFW / Facenet models, no remote service required. Long-running TensorFlow inference can accumulate C-allocated tensor memory until GC finalizers run, so PhotoPrism periodically triggers garbage collection to return that memory to the OS; tune with `PHOTOPRISM_TF_GC_EVERY` (default **200**, `0` disables). Lower values reduce peak RSS but increase GC overhead and can slow indexing, so keep the default unless memory pressure is severe. - **Ollama** — local or proxied multimodal LLMs. See [`ollama/README.md`](ollama/README.md) for tuning and schema details. The engine defaults to `${OLLAMA_BASE_URL:-http://ollama:11434}/api/generate`, trimming any trailing slash on the base URL; set `OLLAMA_BASE_URL=https://ollama.com` to opt into cloud defaults. - **OpenAI** — cloud Responses API. See [`openai/README.md`](openai/README.md) for prompts, schema variants, and header requirements. @@ -199,6 +199,10 @@ Models: - **Ollama**: private, GPU/CPU-hosted multimodal LLMs; best for richer captions/labels without cloud traffic. - **OpenAI**: highest quality reasoning and multimodal support; requires API key and network access. +### Model Unload on Idle + +PhotoPrism currently keeps TensorFlow models resident for the lifetime of the process to avoid repeated load costs. A future “model unload on idle” mode would track last-use timestamps and close the TensorFlow session/graph after a configurable idle period, releasing the model’s memory footprint back to the OS. The trade-off is higher latency and CPU overhead when a model is used again, plus extra I/O to reload weights. This may be attractive for low-frequency or memory-constrained deployments but would slow continuous indexing jobs, so it is not enabled today. + ### Related Docs - Ollama specifics: [`internal/ai/vision/ollama/README.md`](ollama/README.md) diff --git a/internal/ai/vision/api_ollama.go b/internal/ai/vision/api_ollama.go index 8231234c6..85c780060 100644 --- a/internal/ai/vision/api_ollama.go +++ b/internal/ai/vision/api_ollama.go @@ -17,10 +17,13 @@ func NewApiRequestOllama(images Files, fileScheme scheme.Type) (*ApiRequest, err for i := range images { switch fileScheme { case scheme.Data, scheme.Base64: - if file, err := os.Open(images[i]); err != nil { + file, err := os.Open(images[i]) + if err != nil { return nil, fmt.Errorf("%s (create data url)", err) - } else { - imagesData[i] = media.DataBase64(file) + } + imagesData[i] = media.DataBase64(file) + if err := file.Close(); err != nil { + return nil, fmt.Errorf("%s (close data url)", err) } default: return nil, fmt.Errorf("unsupported file scheme %s", clean.Log(fileScheme)) diff --git a/internal/ai/vision/api_request.go b/internal/ai/vision/api_request.go index af990396e..5bab243e7 100644 --- a/internal/ai/vision/api_request.go +++ b/internal/ai/vision/api_request.go @@ -132,10 +132,13 @@ func NewApiRequestImages(images Files, fileScheme scheme.Type) (*ApiRequest, err imageUrls[i] = fmt.Sprintf("%s/%s", DownloadUrl, fileUuid) } case scheme.Data: - if file, err := os.Open(images[i]); err != nil { + file, err := os.Open(images[i]) + if err != nil { return nil, fmt.Errorf("%s (create data url)", err) - } else { - imageUrls[i] = media.DataUrl(file) + } + imageUrls[i] = media.DataUrl(file) + if err := file.Close(); err != nil { + return nil, fmt.Errorf("%s (close data url)", err) } default: return nil, fmt.Errorf("unsupported file scheme %s", clean.Log(fileScheme)) diff --git a/internal/commands/commands.go b/internal/commands/commands.go index 410bbc319..9854bce7c 100644 --- a/internal/commands/commands.go +++ b/internal/commands/commands.go @@ -67,6 +67,7 @@ var PhotoPrism = []*cli.Command{ MomentsCommand, ConvertCommand, ThumbsCommand, + VideosCommands, MigrateCommand, MigrationsCommands, BackupCommand, diff --git a/internal/commands/find.go b/internal/commands/find.go index 0a04bbe28..5395f61d1 100644 --- a/internal/commands/find.go +++ b/internal/commands/find.go @@ -70,7 +70,7 @@ func findAction(ctx *cli.Context) error { return nil } - cols := []string{"File Name", "Mime Type", "Size", "SHA1 Hash"} + cols := []string{"File Name", "Mime Type", "Size", "Checksum"} rows := make([][]string, 0, len(results)) for _, found := range results { diff --git a/internal/commands/video_helpers.go b/internal/commands/video_helpers.go new file mode 100644 index 000000000..1fa7b24bb --- /dev/null +++ b/internal/commands/video_helpers.go @@ -0,0 +1,388 @@ +package commands + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "strconv" + "strings" + "time" + + "github.com/dustin/go-humanize" + + "github.com/photoprism/photoprism/internal/entity" + "github.com/photoprism/photoprism/internal/entity/search" + "github.com/photoprism/photoprism/pkg/media/video" + "github.com/photoprism/photoprism/pkg/txt/report" +) + +// videoNormalizeFilter converts CLI args into a search query, mapping bare tokens to name/filename filters. +func videoNormalizeFilter(args []string) string { + parts := make([]string, 0, len(args)) + + for _, arg := range args { + token := strings.TrimSpace(arg) + if token == "" { + continue + } + + if strings.Contains(token, ":") { + parts = append(parts, token) + continue + } + + if strings.Contains(token, "/") { + parts = append(parts, fmt.Sprintf("filename:%s", token)) + } else { + parts = append(parts, fmt.Sprintf("name:%s", token)) + } + } + + return strings.TrimSpace(strings.Join(parts, " ")) +} + +// videoSplitTrimArgs separates filter args from the trailing trim duration argument. +func videoSplitTrimArgs(args []string) ([]string, string, error) { + if len(args) == 0 { + return nil, "", fmt.Errorf("missing duration argument") + } + + filterArgs := make([]string, len(args)-1) + copy(filterArgs, args[:len(args)-1]) + + durationArg := strings.TrimSpace(args[len(args)-1]) + if durationArg == "" { + return nil, "", fmt.Errorf("missing duration argument") + } + + return filterArgs, durationArg, nil +} + +// videoParseTrimDuration parses the trim duration string with the precedence and rules from the spec. +func videoParseTrimDuration(value string) (time.Duration, error) { + raw := strings.TrimSpace(value) + if raw == "" { + return 0, fmt.Errorf("duration is empty") + } + + sign := 1 + if strings.HasPrefix(raw, "-") { + sign = -1 + raw = strings.TrimSpace(strings.TrimPrefix(raw, "-")) + } + + if raw == "" { + return 0, fmt.Errorf("duration is empty") + } + + if isDigits(raw) { + secs, err := strconv.ParseInt(raw, 10, 64) + if err != nil { + return 0, fmt.Errorf("invalid duration %q", value) + } + if secs == 0 { + return 0, fmt.Errorf("duration must be non-zero") + } + return time.Duration(sign) * time.Duration(secs) * time.Second, nil + } + + if strings.Contains(raw, ":") { + if strings.ContainsAny(raw, "hms") { + return 0, fmt.Errorf("invalid duration %q", value) + } + + parts := strings.Split(raw, ":") + if len(parts) != 2 && len(parts) != 3 { + return 0, fmt.Errorf("invalid duration %q", value) + } + + for _, p := range parts { + if !isDigits(p) { + return 0, fmt.Errorf("invalid duration %q", value) + } + } + + if len(parts) == 2 && len(parts[1]) != 2 { + return 0, fmt.Errorf("invalid duration %q", value) + } + + if len(parts) == 3 && (len(parts[1]) != 2 || len(parts[2]) != 2) { + return 0, fmt.Errorf("invalid duration %q", value) + } + + var hours, minutes, seconds int64 + + if len(parts) == 2 { + minutes, _ = strconv.ParseInt(parts[0], 10, 64) + seconds, _ = strconv.ParseInt(parts[1], 10, 64) + } else { + hours, _ = strconv.ParseInt(parts[0], 10, 64) + minutes, _ = strconv.ParseInt(parts[1], 10, 64) + seconds, _ = strconv.ParseInt(parts[2], 10, 64) + } + + total := time.Duration(hours)*time.Hour + time.Duration(minutes)*time.Minute + time.Duration(seconds)*time.Second + if total == 0 { + return 0, fmt.Errorf("duration must be non-zero") + } + + return time.Duration(sign) * total, nil + } + + parsed, err := time.ParseDuration(applySign(raw, sign)) + if err != nil { + return 0, fmt.Errorf("invalid duration %q", value) + } + + if parsed == 0 { + return 0, fmt.Errorf("duration must be non-zero") + } + + return parsed, nil +} + +// videoListColumns returns the ordered column list for the video ls output. +func videoListColumns() []string { + return []string{"Video", "Size", "Resolution", "Duration", "Frames", "FPS", "Content Type", "Checksum"} +} + +// videoResultFiles returns the related files for a merged search result or falls back to the file fields on the result. +func videoResultFiles(found search.Photo) []entity.File { + if len(found.Files) > 0 { + return found.Files + } + + return []entity.File{videoFileFromSearch(found)} +} + +// videoFileFromSearch builds a file record from the file fields of a search result. +func videoFileFromSearch(found search.Photo) entity.File { + return entity.File{ + ID: found.FileID, + PhotoUID: found.PhotoUID, + FileUID: found.FileUID, + FileRoot: found.FileRoot, + FileName: found.FileName, + OriginalName: found.OriginalName, + FileHash: found.FileHash, + FileWidth: found.FileWidth, + FileHeight: found.FileHeight, + FilePortrait: found.FilePortrait, + FilePrimary: found.FilePrimary, + FileSidecar: found.FileSidecar, + FileMissing: found.FileMissing, + FileVideo: found.FileVideo, + FileDuration: found.FileDuration, + FileFPS: found.FileFPS, + FileFrames: found.FileFrames, + FilePages: found.FilePages, + FileCodec: found.FileCodec, + FileType: found.FileType, + MediaType: found.MediaType, + FileMime: found.FileMime, + FileSize: found.FileSize, + FileOrientation: found.FileOrientation, + FileProjection: found.FileProjection, + FileAspectRatio: found.FileAspectRatio, + FileColors: found.FileColors, + FileDiff: found.FileDiff, + FileChroma: found.FileChroma, + FileLuminance: found.FileLuminance, + OmitMarkers: true, + } +} + +// videoPrimaryFile selects the best video file from a merged search result, preferring non-sidecar entries. +func videoPrimaryFile(found search.Photo) (entity.File, bool) { + files := videoResultFiles(found) + if len(files) == 0 { + return entity.File{}, false + } + + for _, file := range files { + if file.FileVideo && !file.FileSidecar { + return file, true + } + } + + for _, file := range files { + if file.FileVideo { + return file, true + } + } + + return files[0], true +} + +// videoListRow renders a search result row for table outputs with human-friendly values. +func videoListRow(found search.Photo) []string { + videoFile, _ := videoPrimaryFile(found) + + row := []string{ + videoFile.FileName, + videoHumanSize(videoFile.FileSize), + fmt.Sprintf("%dx%d", videoFile.FileWidth, videoFile.FileHeight), + videoHumanDuration(videoFile.FileDuration), + videoHumanInt(videoFile.FileFrames), + videoHumanFloat(videoFile.FileFPS), + video.ContentType(videoFile.FileMime, videoFile.FileType, videoFile.FileCodec, videoFile.FileHDR), + videoFile.FileHash, + } + + return row +} + +// videoListJSONRow renders a search result row for JSON output with canonical column keys. +func videoListJSONRow(found search.Photo) map[string]interface{} { + videoFile, _ := videoPrimaryFile(found) + + data := map[string]interface{}{ + "video": videoFile.FileName, + "size": videoNonNegativeSize(videoFile.FileSize), + "resolution": fmt.Sprintf("%dx%d", videoFile.FileWidth, videoFile.FileHeight), + "duration": videoFile.FileDuration.Nanoseconds(), + "frames": videoFile.FileFrames, + "fps": videoFile.FileFPS, + "content_type": video.ContentType(videoFile.FileMime, videoFile.FileType, videoFile.FileCodec, videoFile.FileHDR), + "checksum": videoFile.FileHash, + } + + return data +} + +// videoListJSON marshals a list of JSON rows using the canonical keys for each column. +func videoListJSON(rows []map[string]interface{}, cols []string) (string, error) { + canon := make([]string, len(cols)) + for i, col := range cols { + canon[i] = report.CanonKey(col) + } + + payload := make([]map[string]interface{}, 0, len(rows)) + + for _, row := range rows { + item := make(map[string]interface{}, len(canon)) + for _, key := range canon { + item[key] = row[key] + } + payload = append(payload, item) + } + + data, err := json.Marshal(payload) + if err != nil { + return "", err + } + + return string(data), nil +} + +// videoHumanDuration formats a duration for human-readable tables. +func videoHumanDuration(d time.Duration) string { + if d <= 0 { + return "" + } + + return d.String() +} + +// videoHumanInt formats non-zero integers for human-readable tables. +func videoHumanInt(value int) string { + if value <= 0 { + return "" + } + + return strconv.Itoa(value) +} + +// videoHumanFloat formats non-zero floats without unnecessary trailing zeros. +func videoHumanFloat(value float64) string { + if value <= 0 { + return "" + } + + return strconv.FormatFloat(value, 'f', -1, 64) +} + +// videoHumanSize formats file sizes with human-readable units. +func videoHumanSize(size int64) string { + return humanize.Bytes(uint64(videoNonNegativeSize(size))) //nolint:gosec // size is bounded to non-negative values +} + +// videoNonNegativeSize clamps negative sizes to zero before formatting. +func videoNonNegativeSize(size int64) int64 { + if size < 0 { + return 0 + } + + return size +} + +// videoTempPath creates a temporary file path in the destination directory. +func videoTempPath(dir, pattern string) (string, error) { + if dir == "" { + return "", fmt.Errorf("temp directory is empty") + } + + tmpFile, err := os.CreateTemp(dir, pattern) + if err != nil { + return "", err + } + + if err = tmpFile.Close(); err != nil { + return "", err + } + + if err = os.Remove(tmpFile.Name()); err != nil { + return "", err + } + + return tmpFile.Name(), nil +} + +// videoFFmpegSeconds converts a duration into an ffmpeg-friendly seconds string. +func videoFFmpegSeconds(d time.Duration) string { + seconds := d.Seconds() + return strconv.FormatFloat(seconds, 'f', 3, 64) +} + +// isDigits reports whether the string contains only decimal digits. +func isDigits(value string) bool { + if value == "" { + return false + } + + for _, r := range value { + if r < '0' || r > '9' { + return false + } + } + + return true +} + +// applySign applies a numeric sign to a duration string for parsing. +func applySign(value string, sign int) string { + if sign >= 0 { + return value + } + + return "-" + value +} + +// videoSidecarPath builds the sidecar destination path for an originals file without creating directories. +func videoSidecarPath(srcName, originalsPath, sidecarPath string) string { + src := filepath.ToSlash(srcName) + orig := filepath.ToSlash(originalsPath) + + if orig != "" { + orig = strings.TrimSuffix(orig, "/") + "/" + } + + rel := strings.TrimPrefix(src, orig) + if rel == src { + rel = filepath.Base(srcName) + } + + rel = strings.TrimPrefix(rel, "/") + return filepath.Join(sidecarPath, filepath.FromSlash(rel)) +} diff --git a/internal/commands/video_helpers_test.go b/internal/commands/video_helpers_test.go new file mode 100644 index 000000000..35c6f7e22 --- /dev/null +++ b/internal/commands/video_helpers_test.go @@ -0,0 +1,98 @@ +package commands + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "github.com/photoprism/photoprism/internal/entity" + "github.com/photoprism/photoprism/internal/entity/search" + "github.com/photoprism/photoprism/pkg/media/video" +) + +func TestVideoNormalizeFilter(t *testing.T) { + t.Run("NormalizeTokens", func(t *testing.T) { + args := []string{"foo", "2024/clip.mp4", "name:bar", "filename:2025/a.mov", ""} + expected := "name:foo filename:2024/clip.mp4 name:bar filename:2025/a.mov" + assert.Equal(t, expected, videoNormalizeFilter(args)) + }) +} + +func TestVideoParseTrimDuration(t *testing.T) { + t.Run("Seconds", func(t *testing.T) { + d, err := videoParseTrimDuration("5") + assert.NoError(t, err) + assert.Equal(t, 5*time.Second, d) + }) + t.Run("NegativeSeconds", func(t *testing.T) { + d, err := videoParseTrimDuration("-10") + assert.NoError(t, err) + assert.Equal(t, -10*time.Second, d) + }) + t.Run("MinutesSeconds", func(t *testing.T) { + d, err := videoParseTrimDuration("02:05") + assert.NoError(t, err) + assert.Equal(t, 2*time.Minute+5*time.Second, d) + }) + t.Run("HoursMinutesSeconds", func(t *testing.T) { + d, err := videoParseTrimDuration("01:02:03") + assert.NoError(t, err) + assert.Equal(t, time.Hour+2*time.Minute+3*time.Second, d) + }) + t.Run("GoDuration", func(t *testing.T) { + d, err := videoParseTrimDuration("2m5s") + assert.NoError(t, err) + assert.Equal(t, 2*time.Minute+5*time.Second, d) + }) + t.Run("Invalid", func(t *testing.T) { + _, err := videoParseTrimDuration("1:30s") + assert.Error(t, err) + }) +} + +func TestVideoListJSONRow(t *testing.T) { + t.Run("NumericFields", func(t *testing.T) { + found := search.Photo{ + Files: []entity.File{ + { + FileName: "clip.avc", + FileRoot: "/", + FileDuration: time.Second, + FileCodec: "avc1", + FileMime: "video/mp4", + FileWidth: 640, + FileHeight: 360, + FileFPS: 24, + FileFrames: 24, + FileSize: 42, + FileHash: "sidecar", + FileSidecar: true, + FileVideo: true, + }, + { + FileName: "clip.mp4", + FileRoot: "/", + FileDuration: 2 * time.Second, + FileCodec: "avc1", + FileMime: "video/mp4", + FileWidth: 1920, + FileHeight: 1080, + FileFPS: 29.97, + FileFrames: 120, + FileSize: 1234, + FileHash: "abc", + FileVideo: true, + }, + }, + } + + row := videoListJSONRow(found) + assert.Equal(t, "clip.mp4", row["video"]) + assert.Equal(t, int64(2*time.Second), row["duration"]) + assert.Equal(t, int64(1234), row["size"]) + assert.Equal(t, "1920x1080", row["resolution"]) + assert.Equal(t, video.ContentType("video/mp4", "", "avc1", false), row["content_type"]) + assert.Equal(t, "abc", row["checksum"]) + }) +} diff --git a/internal/commands/video_index.go b/internal/commands/video_index.go new file mode 100644 index 000000000..11527fe48 --- /dev/null +++ b/internal/commands/video_index.go @@ -0,0 +1,34 @@ +package commands + +import ( + "fmt" + + "github.com/photoprism/photoprism/internal/config" + "github.com/photoprism/photoprism/internal/photoprism" + "github.com/photoprism/photoprism/internal/photoprism/get" +) + +// videoReindexRelated reindexes the related file group for the given main file. +func videoReindexRelated(conf *config.Config, fileName string) error { + if fileName == "" { + return fmt.Errorf("index: missing filename") + } + + mediaFile, err := photoprism.NewMediaFile(fileName) + if err != nil { + return err + } + + related, err := mediaFile.RelatedFiles(conf.Settings().Stack.Name) + if err != nil { + return err + } + + index := get.Index() + result := photoprism.IndexRelated(related, index, photoprism.IndexOptionsSingle(conf)) + if result.Err != nil { + return result.Err + } + + return nil +} diff --git a/internal/commands/video_info.go b/internal/commands/video_info.go new file mode 100644 index 000000000..73bbd22be --- /dev/null +++ b/internal/commands/video_info.go @@ -0,0 +1,223 @@ +package commands + +import ( + "bytes" + "encoding/json" + "fmt" + "os/exec" + "strings" + + "github.com/urfave/cli/v2" + + "github.com/photoprism/photoprism/internal/config" + "github.com/photoprism/photoprism/internal/entity" + "github.com/photoprism/photoprism/internal/entity/search" + "github.com/photoprism/photoprism/internal/meta" + "github.com/photoprism/photoprism/internal/photoprism" + "github.com/photoprism/photoprism/pkg/clean" +) + +// VideoInfoCommand configures the command name, flags, and action. +var VideoInfoCommand = &cli.Command{ + Name: "info", + Usage: "Displays diagnostic information for indexed videos", + ArgsUsage: "[filter]...", + Flags: []cli.Flag{ + videoCountFlag, + OffsetFlag, + JsonFlag(), + videoVerboseFlag, + }, + Action: videoInfoAction, +} + +// videoInfoAction prints indexed, ExifTool, and ffprobe metadata for matching videos. +func videoInfoAction(ctx *cli.Context) error { + return CallWithDependencies(ctx, func(conf *config.Config) error { + filter := videoNormalizeFilter(ctx.Args().Slice()) + results, err := videoSearchResults(filter, ctx.Int(videoCountFlag.Name), ctx.Int(OffsetFlag.Name)) + if err != nil { + return err + } + + entries := make([]videoInfoEntry, 0, len(results)) + for _, found := range results { + entry, err := videoInfoEntryFor(conf, found, ctx.Bool(videoVerboseFlag.Name)) + if err != nil { + log.Warnf("info: %s", clean.Error(err)) + } + entries = append(entries, entry) + } + + if ctx.Bool("json") { + payload, err := json.Marshal(entries) + if err != nil { + return err + } + fmt.Println(string(payload)) + return nil + } + + for _, entry := range entries { + videoPrintInfo(entry) + } + + return nil + }) +} + +// videoInfoEntry describes all metadata sections for a single video. +type videoInfoEntry struct { + Index map[string]interface{} `json:"index"` + Exif interface{} `json:"exif,omitempty"` + FFprobe interface{} `json:"ffprobe,omitempty"` + Raw map[string]string `json:"raw,omitempty"` +} + +// videoInfoEntryFor collects indexed, ExifTool, and ffprobe metadata for a search result. +func videoInfoEntryFor(conf *config.Config, found search.Photo, verbose bool) (videoInfoEntry, error) { + videoFile, ok := videoPrimaryFile(found) + if !ok { + return videoInfoEntry{}, fmt.Errorf("info: missing video file for %s", found.PhotoUID) + } + + entry := videoInfoEntry{ + Index: videoIndexSummary(found, videoFile), + } + + filePath := photoprism.FileName(videoFile.FileRoot, videoFile.FileName) + mediaFile, err := photoprism.NewMediaFile(filePath) + if err != nil { + return entry, err + } + + if conf.DisableExifTool() { + entry.Exif = nil + } else { + exif := mediaFile.MetaData() + entry.Exif = exif + if verbose { + entry.ensureRaw() + entry.Raw["exif"] = videoPrettyJSON(exif) + } + } + + ffprobeBin := conf.FFprobeBin() + if ffprobeBin == "" { + entry.FFprobe = nil + } else if ffprobe, raw, err := videoRunFFprobe(ffprobeBin, filePath); err != nil { + entry.FFprobe = nil + if verbose { + entry.ensureRaw() + entry.Raw["ffprobe"] = raw + } + } else { + entry.FFprobe = ffprobe + if verbose { + entry.ensureRaw() + entry.Raw["ffprobe"] = raw + } + } + + return entry, nil +} + +// videoIndexSummary builds a concise map of indexed fields for diagnostics. +func videoIndexSummary(found search.Photo, file entity.File) map[string]interface{} { + return map[string]interface{}{ + "file_name": file.FileName, + "file_root": file.FileRoot, + "file_uid": file.FileUID, + "photo_uid": found.PhotoUID, + "media_type": file.MediaType, + "file_type": file.FileType, + "file_mime": file.FileMime, + "file_codec": file.FileCodec, + "file_hash": file.FileHash, + "file_size": file.FileSize, + "file_duration": file.FileDuration.Nanoseconds(), + "photo_duration": found.PhotoDuration.Nanoseconds(), + "file_frames": file.FileFrames, + "file_fps": file.FileFPS, + "file_width": file.FileWidth, + "file_height": file.FileHeight, + "file_sidecar": file.FileSidecar, + "file_missing": file.FileMissing, + "file_video": file.FileVideo, + "original_name": file.OriginalName, + "instance_id": file.InstanceID, + "photo_taken_at": found.TakenAt, + "photo_taken_src": found.TakenSrc, + } +} + +// videoRunFFprobe executes ffprobe and returns parsed JSON plus raw output. +func videoRunFFprobe(ffprobeBin, filePath string) (interface{}, string, error) { + cmd := exec.Command(ffprobeBin, "-v", "quiet", "-print_format", "json", "-show_format", "-show_streams", filePath) //nolint:gosec // args are validated paths + + var stdout bytes.Buffer + var stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + + if err := cmd.Run(); err != nil { + return nil, strings.TrimSpace(stdout.String()), fmt.Errorf("ffprobe failed: %s", strings.TrimSpace(stderr.String())) + } + + raw := strings.TrimSpace(stdout.String()) + if raw == "" { + return nil, raw, nil + } + + var data interface{} + if err := json.Unmarshal([]byte(raw), &data); err != nil { + return nil, raw, nil + } + + return data, raw, nil +} + +// ensureRaw initializes the raw map for verbose output. +func (v *videoInfoEntry) ensureRaw() { + if v.Raw == nil { + v.Raw = make(map[string]string) + } +} + +// videoPrettyJSON returns indented JSON for human-readable output. +func videoPrettyJSON(value interface{}) string { + data, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "" + } + + return string(data) +} + +// videoPrintInfo prints a human-readable metadata summary to stdout. +func videoPrintInfo(entry videoInfoEntry) { + fmt.Println("Indexed Metadata:") + fmt.Println(videoPrettyJSON(entry.Index)) + + if entry.Exif == nil { + fmt.Println("ExifTool Metadata: disabled or unavailable") + } else if exifMap, ok := entry.Exif.(meta.Data); ok { + fmt.Println("ExifTool Metadata:") + fmt.Println(videoPrettyJSON(exifMap)) + } else { + fmt.Println("ExifTool Metadata:") + fmt.Println(videoPrettyJSON(entry.Exif)) + } + + if entry.FFprobe == nil { + fmt.Println("FFprobe Diagnostics: unavailable") + } else { + fmt.Println("FFprobe Diagnostics:") + fmt.Println(videoPrettyJSON(entry.FFprobe)) + } + + if len(entry.Raw) > 0 { + fmt.Println("Raw Metadata:") + fmt.Println(videoPrettyJSON(entry.Raw)) + } +} diff --git a/internal/commands/video_ls.go b/internal/commands/video_ls.go new file mode 100644 index 000000000..b36f5d2e8 --- /dev/null +++ b/internal/commands/video_ls.go @@ -0,0 +1,78 @@ +package commands + +import ( + "fmt" + + "github.com/urfave/cli/v2" + + "github.com/photoprism/photoprism/internal/config" + "github.com/photoprism/photoprism/pkg/txt/report" +) + +// VideoListCommand configures the command name, flags, and action. +var VideoListCommand = &cli.Command{ + Name: "ls", + Usage: "Lists indexed video files matching the specified filters", + ArgsUsage: "[filter]...", + Flags: append(append([]cli.Flag{}, report.CliFlags...), + videoCountFlag, + OffsetFlag, + ), + Action: videoListAction, +} + +// videoListAction renders a filtered list of indexed video files. +func videoListAction(ctx *cli.Context) error { + return CallWithDependencies(ctx, func(conf *config.Config) error { + // Ensure config is initialized before querying the index. + if conf == nil { + return fmt.Errorf("config is not available") + } + + format, err := report.CliFormatStrict(ctx) + + if err != nil { + return err + } + + filter := videoNormalizeFilter(ctx.Args().Slice()) + results, err := videoSearchResults(filter, ctx.Int(videoCountFlag.Name), ctx.Int(OffsetFlag.Name)) + + if err != nil { + return err + } + + cols := videoListColumns() + + if format == report.JSON { + rows := make([]map[string]interface{}, 0, len(results)) + for _, found := range results { + rows = append(rows, videoListJSONRow(found)) + } + + payload, jsonErr := videoListJSON(rows, cols) + if jsonErr != nil { + return jsonErr + } + + fmt.Println(payload) + return nil + } + + rows := make([][]string, 0, len(results)) + + for _, found := range results { + rows = append(rows, videoListRow(found)) + } + + output, err := report.RenderFormat(rows, cols, format) + + if err != nil { + return err + } + + fmt.Println(output) + + return nil + }) +} diff --git a/internal/commands/video_remux.go b/internal/commands/video_remux.go new file mode 100644 index 000000000..e4873c670 --- /dev/null +++ b/internal/commands/video_remux.go @@ -0,0 +1,308 @@ +package commands + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/manifoldco/promptui" + "github.com/urfave/cli/v2" + + "github.com/photoprism/photoprism/internal/config" + "github.com/photoprism/photoprism/internal/entity" + "github.com/photoprism/photoprism/internal/entity/search" + "github.com/photoprism/photoprism/internal/ffmpeg" + "github.com/photoprism/photoprism/internal/ffmpeg/encode" + "github.com/photoprism/photoprism/internal/photoprism" + "github.com/photoprism/photoprism/internal/photoprism/get" + "github.com/photoprism/photoprism/pkg/clean" + "github.com/photoprism/photoprism/pkg/fs" + "github.com/photoprism/photoprism/pkg/media/video" +) + +// VideoRemuxCommand configures the command name, flags, and action. +var VideoRemuxCommand = &cli.Command{ + Name: "remux", + Usage: "Remuxes AVC videos into an MP4 container", + ArgsUsage: "[filter]...", + Flags: []cli.Flag{ + videoCountFlag, + OffsetFlag, + videoForceFlag, + DryRunFlag("prints planned remux operations without writing files"), + YesFlag(), + }, + Action: videoRemuxAction, +} + +// videoRemuxAction remuxes matching AVC files into MP4 containers. +func videoRemuxAction(ctx *cli.Context) error { + return CallWithDependencies(ctx, func(conf *config.Config) error { + if conf.DisableFFmpeg() { + return fmt.Errorf("ffmpeg is disabled") + } + + filter := videoNormalizeFilter(ctx.Args().Slice()) + results, err := videoSearchResults(filter, ctx.Int(videoCountFlag.Name), ctx.Int(OffsetFlag.Name)) + if err != nil { + return err + } + + plans, preflight, err := videoBuildRemuxPlans(conf, results, ctx.Bool(videoForceFlag.Name)) + if err != nil { + return err + } + + if len(plans) == 0 { + log.Infof("remux: found no matching videos") + return nil + } + + if !ctx.Bool("dry-run") { + if err = videoCheckFreeSpace(preflight); err != nil { + return err + } + } + + if !ctx.Bool("dry-run") && !RunNonInteractively(ctx.Bool("yes")) { + prompt := promptui.Prompt{ + Label: fmt.Sprintf("Remux %d video files?", len(plans)), + IsConfirm: true, + } + if _, err = prompt.Run(); err != nil { + log.Info("remux: cancelled") + return nil + } + } + + var processed, skipped, failed int + convert := get.Convert() + + for _, plan := range plans { + if ctx.Bool("dry-run") { + log.Infof("remux: would remux %s to %s", clean.Log(plan.SrcPath), clean.Log(plan.DestPath)) + skipped++ + continue + } + + if err = videoRemuxFile(conf, convert, plan, ctx.Bool(videoForceFlag.Name), true); err != nil { + log.Errorf("remux: %s", clean.Error(err)) + failed++ + continue + } + + processed++ + } + + log.Infof("remux: processed %d, skipped %d, failed %d", processed, skipped, failed) + + if failed > 0 { + return fmt.Errorf("remux: %d files failed", failed) + } + + return nil + }) +} + +// videoRemuxPlan holds a resolved remux operation for a single video file. +type videoRemuxPlan struct { + IndexPath string + SrcPath string + DestPath string + SizeBytes int64 + Sidecar bool +} + +// videoBuildRemuxPlans prepares remux operations and preflight size checks from search results. +func videoBuildRemuxPlans(conf *config.Config, results []search.Photo, force bool) ([]videoRemuxPlan, []videoOutputPlan, error) { + plans := make([]videoRemuxPlan, 0, len(results)) + preflight := make([]videoOutputPlan, 0, len(results)) + + for _, found := range results { + videoFile, ok := videoPrimaryFile(found) + if !ok { + log.Warnf("remux: missing video file for %s", clean.Log(found.PhotoUID)) + continue + } + + if videoFile.FileSidecar { + log.Warnf("remux: skipping sidecar file %s", clean.Log(videoFile.FileName)) + continue + } + + if videoFile.MediaType == entity.MediaLive { + log.Warnf("remux: skipping live photo video %s", clean.Log(videoFile.FileName)) + continue + } + + srcPath := photoprism.FileName(videoFile.FileRoot, videoFile.FileName) + if !fs.FileExistsNotEmpty(srcPath) { + log.Warnf("remux: missing file %s", clean.Log(srcPath)) + continue + } + + if !videoCodecIsAvc(videoFile.FileCodec) && !force { + if !videoFallbackCodecAvc(srcPath) { + log.Warnf("remux: skipping non-AVC video %s", clean.Log(videoFile.FileName)) + continue + } + } + + destPath := fs.StripKnownExt(srcPath) + fs.ExtMp4 + useSidecar := false + indexPath := destPath + + if conf.ReadOnly() || !fs.PathWritable(filepath.Dir(srcPath)) || !fs.Writable(srcPath) { + if !conf.SidecarWritable() || !fs.PathWritable(conf.SidecarPath()) { + return nil, nil, config.ErrReadOnly + } + + sidecarBase := videoSidecarPath(srcPath, conf.OriginalsPath(), conf.SidecarPath()) + destPath = fs.StripKnownExt(sidecarBase) + fs.ExtMp4 + useSidecar = true + indexPath = srcPath + } + + if destPath != srcPath && fs.FileExistsNotEmpty(destPath) && !force { + log.Warnf("remux: output already exists %s", clean.Log(destPath)) + continue + } + + plans = append(plans, videoRemuxPlan{ + IndexPath: indexPath, + SrcPath: srcPath, + DestPath: destPath, + SizeBytes: videoFile.FileSize, + Sidecar: useSidecar, + }) + + preflight = append(preflight, videoOutputPlan{ + Destination: destPath, + SizeBytes: videoFile.FileSize, + }) + } + + return plans, preflight, nil +} + +// videoRemuxFile runs ffmpeg remuxing and refreshes previews/thumbnails before reindexing. +func videoRemuxFile(conf *config.Config, convert *photoprism.Convert, plan videoRemuxPlan, force, noBackup bool) error { + tempDir := filepath.Dir(plan.DestPath) + tempPath, err := videoTempPath(tempDir, ".remux-*.mp4") + if err != nil { + return err + } + + opt := encode.NewRemuxOptions(conf.FFmpegBin(), fs.VideoMp4, true) + opt.Force = true + + if err = ffmpeg.RemuxFile(plan.SrcPath, tempPath, opt); err != nil { + return err + } + + if !fs.FileExistsNotEmpty(tempPath) { + _ = os.Remove(tempPath) + return fmt.Errorf("remux output missing for %s", clean.Log(plan.SrcPath)) + } + + if err = os.Chmod(tempPath, fs.ModeFile); err != nil { + return err + } + + if plan.Sidecar { + if fs.FileExists(plan.DestPath) && !force { + _ = os.Remove(tempPath) + return fmt.Errorf("output already exists %s", clean.Log(plan.DestPath)) + } + + if fs.FileExists(plan.DestPath) { + _ = os.Remove(plan.DestPath) + } + + if err = os.Rename(tempPath, plan.DestPath); err != nil { + _ = os.Remove(tempPath) + return err + } + } else { + if plan.DestPath != plan.SrcPath && fs.FileExists(plan.DestPath) && !force { + _ = os.Remove(tempPath) + return fmt.Errorf("output already exists %s", clean.Log(plan.DestPath)) + } + + if noBackup { + if plan.DestPath != plan.SrcPath { + _ = os.Remove(plan.DestPath) + } + } else { + backupPath := plan.SrcPath + ".backup" + if fs.FileExists(backupPath) { + _ = os.Remove(backupPath) + } + if err = os.Rename(plan.SrcPath, backupPath); err != nil { + _ = os.Remove(tempPath) + return err + } + _ = os.Chmod(backupPath, fs.ModeBackupFile) + } + + if plan.DestPath != plan.SrcPath && fs.FileExists(plan.DestPath) { + _ = os.Remove(plan.DestPath) + } + + if err = os.Rename(tempPath, plan.DestPath); err != nil { + _ = os.Remove(tempPath) + return err + } + } + + mediaFile, err := photoprism.NewMediaFile(plan.DestPath) + if err != nil { + return err + } + + if convert != nil { + if img, imgErr := convert.ToImage(mediaFile, true); imgErr != nil { + log.Warnf("remux: %s", clean.Error(imgErr)) + } else if img != nil { + if thumbsErr := img.GenerateThumbnails(conf.ThumbCachePath(), true); thumbsErr != nil { + log.Warnf("remux: %s", clean.Error(thumbsErr)) + } + } + } + + return videoReindexRelated(conf, plan.IndexPath) +} + +// videoCodecIsAvc reports whether a codec string maps to an AVC/H.264 variant. +func videoCodecIsAvc(codec string) bool { + value := strings.ToLower(strings.TrimSpace(codec)) + if value == "" { + return false + } + + if value == "h264" || value == "x264" { + return true + } + + switch video.Codecs[value] { + case video.CodecAvc1, video.CodecAvc2, video.CodecAvc3, video.CodecAvc4: + return true + default: + return false + } +} + +// videoFallbackCodecAvc probes codec metadata when the indexed codec is missing. +func videoFallbackCodecAvc(srcPath string) bool { + mediaFile, err := photoprism.NewMediaFile(srcPath) + if err != nil { + return false + } + + if info := mediaFile.VideoInfo(); info.VideoCodec != "" { + return videoCodecIsAvc(info.VideoCodec) + } + + return mediaFile.MetaData().CodecAvc() +} diff --git a/internal/commands/video_search.go b/internal/commands/video_search.go new file mode 100644 index 000000000..b9ba0663e --- /dev/null +++ b/internal/commands/video_search.go @@ -0,0 +1,153 @@ +package commands + +import ( + "fmt" + + "github.com/photoprism/photoprism/internal/entity" + "github.com/photoprism/photoprism/internal/entity/search" + "github.com/photoprism/photoprism/internal/entity/sortby" + "github.com/photoprism/photoprism/internal/form" +) + +// videoSearchResults runs a video-only search and applies offset/count after merging related files. +func videoSearchResults(query string, count int, offset int) ([]search.Photo, error) { + if offset < 0 { + offset = 0 + } + + if count <= 0 { + return []search.Photo{}, nil + } + + frm := form.SearchPhotos{ + Query: query, + Primary: false, + Merged: true, + Video: true, + Order: sortby.Name, + } + + target := count + offset + + if target < 0 { + target = 0 + } + + collected := make([]search.Photo, 0, target) + index := make(map[string]int, target) + searchOffset := 0 + batchSize := count + + if batchSize < 200 { + batchSize = 200 + } + + needComplete := false + + for len(collected) < target || needComplete { + frm.Count = batchSize + frm.Offset = searchOffset + + results, rawCount, err := search.Photos(frm) + + if err != nil { + return nil, err + } + + if len(results) == 0 || rawCount == 0 { + break + } + + for _, found := range results { + key := videoSearchKey(found) + if idx, ok := index[key]; ok { + collected[idx].Files = videoMergeFiles(collected[idx].Files, found.Files) + if len(collected[idx].Files) > 1 { + collected[idx].Merged = true + } + continue + } + + collected = append(collected, found) + index[key] = len(collected) - 1 + } + + searchOffset += rawCount + + needComplete = false + if len(collected) >= target { + lastNeededKey := videoSearchKey(collected[target-1]) + lastBatchKey := videoSearchKey(results[len(results)-1]) + if lastNeededKey == lastBatchKey && rawCount == batchSize { + needComplete = true + } + } + + if rawCount < batchSize { + break + } + } + + if offset >= len(collected) { + return []search.Photo{}, nil + } + + end := offset + count + + if end > len(collected) { + end = len(collected) + } + + return collected[offset:end], nil +} + +// videoSearchKey returns a stable key for de-duplicating merged photo results. +func videoSearchKey(found search.Photo) string { + if found.ID > 0 { + return fmt.Sprintf("id:%d", found.ID) + } + + return found.PhotoUID +} + +// videoMergeFiles appends unique files from additions into the existing list. +func videoMergeFiles(existing []entity.File, additions []entity.File) []entity.File { + if len(additions) == 0 { + return existing + } + + if len(existing) == 0 { + return additions + } + + seen := make(map[string]struct{}, len(existing)) + for _, file := range existing { + seen[videoFileKey(file)] = struct{}{} + } + + for _, file := range additions { + key := videoFileKey(file) + if _, ok := seen[key]; ok { + continue + } + existing = append(existing, file) + seen[key] = struct{}{} + } + + return existing +} + +// videoFileKey returns a stable identifier for a file entry when merging search results. +func videoFileKey(file entity.File) string { + if file.FileUID != "" { + return "uid:" + file.FileUID + } + if file.ID != 0 { + return fmt.Sprintf("id:%d", file.ID) + } + if file.FileHash != "" { + return "hash:" + file.FileHash + } + + return fmt.Sprintf("name:%s/%s:%d", file.FileRoot, file.FileName, file.FileSize) +} diff --git a/internal/commands/video_storage.go b/internal/commands/video_storage.go new file mode 100644 index 000000000..406515a87 --- /dev/null +++ b/internal/commands/video_storage.go @@ -0,0 +1,48 @@ +package commands + +import ( + "fmt" + "path/filepath" + + "github.com/dustin/go-humanize" + + "github.com/photoprism/photoprism/pkg/clean" + "github.com/photoprism/photoprism/pkg/fs/duf" +) + +// videoOutputPlan describes a planned output file for preflight checks. +type videoOutputPlan struct { + Destination string + SizeBytes int64 +} + +// videoCheckFreeSpace validates that destination filesystems have enough free space for outputs. +func videoCheckFreeSpace(plans []videoOutputPlan) error { + required := make(map[string]uint64) + + for _, plan := range plans { + if plan.Destination == "" { + continue + } + + dir := filepath.Dir(plan.Destination) + required[dir] += uint64(videoNonNegativeSize(plan.SizeBytes)) //nolint:gosec // size is clamped to non-negative values + } + + for dir, size := range required { + mount, err := duf.PathInfo(dir) + if err != nil { + return err + } + + if mount.Free < size { + return fmt.Errorf("insufficient free space in %s: need %s, have %s", + clean.Log(dir), + humanize.Bytes(size), + humanize.Bytes(mount.Free), + ) + } + } + + return nil +} diff --git a/internal/commands/video_transcode.go b/internal/commands/video_transcode.go new file mode 100644 index 000000000..328b2828a --- /dev/null +++ b/internal/commands/video_transcode.go @@ -0,0 +1,215 @@ +package commands + +import ( + "fmt" + "os" + + "github.com/manifoldco/promptui" + "github.com/urfave/cli/v2" + + "github.com/photoprism/photoprism/internal/config" + "github.com/photoprism/photoprism/internal/entity" + "github.com/photoprism/photoprism/internal/entity/search" + "github.com/photoprism/photoprism/internal/photoprism" + "github.com/photoprism/photoprism/internal/photoprism/get" + "github.com/photoprism/photoprism/pkg/clean" + "github.com/photoprism/photoprism/pkg/fs" +) + +// VideoTranscodeCommand configures the command name, flags, and action. +var VideoTranscodeCommand = &cli.Command{ + Name: "transcode", + Usage: "Transcodes matching videos to AVC sidecar files", + ArgsUsage: "[filter]...", + Flags: []cli.Flag{ + videoCountFlag, + OffsetFlag, + videoForceFlag, + DryRunFlag("prints planned transcode operations without writing files"), + YesFlag(), + }, + Action: videoTranscodeAction, +} + +// videoTranscodeAction transcodes matching videos into sidecar AVC files. +func videoTranscodeAction(ctx *cli.Context) error { + return CallWithDependencies(ctx, func(conf *config.Config) error { + if conf.DisableFFmpeg() { + return fmt.Errorf("ffmpeg is disabled") + } + + filter := videoNormalizeFilter(ctx.Args().Slice()) + results, err := videoSearchResults(filter, ctx.Int(videoCountFlag.Name), ctx.Int(OffsetFlag.Name)) + if err != nil { + return err + } + + plans, preflight, err := videoBuildTranscodePlans(conf, results, ctx.Bool(videoForceFlag.Name)) + if err != nil { + return err + } + + if len(plans) == 0 { + log.Infof("transcode: found no matching videos") + return nil + } + + if !ctx.Bool("dry-run") { + if err = videoCheckFreeSpace(preflight); err != nil { + return err + } + } + + if !ctx.Bool("dry-run") && !RunNonInteractively(ctx.Bool("yes")) { + prompt := promptui.Prompt{ + Label: fmt.Sprintf("Transcode %d video files?", len(plans)), + IsConfirm: true, + } + if _, err = prompt.Run(); err != nil { + log.Info("transcode: cancelled") + return nil + } + } + + var processed, skipped, failed int + convert := get.Convert() + + for _, plan := range plans { + if ctx.Bool("dry-run") { + log.Infof("transcode: would transcode %s to %s", clean.Log(plan.SrcPath), clean.Log(plan.DestPath)) + skipped++ + continue + } + + file, err := videoTranscodeFile(conf, convert, plan, ctx.Bool(videoForceFlag.Name)) + if err != nil { + log.Errorf("transcode: %s", clean.Error(err)) + failed++ + continue + } + + if file != nil { + if chmodErr := os.Chmod(file.FileName(), fs.ModeFile); chmodErr != nil { + log.Warnf("transcode: %s", clean.Error(chmodErr)) + } + } + + if err = videoReindexRelated(conf, plan.IndexPath); err != nil { + log.Errorf("transcode: %s", clean.Error(err)) + failed++ + continue + } + + processed++ + } + + log.Infof("transcode: processed %d, skipped %d, failed %d", processed, skipped, failed) + + if failed > 0 { + return fmt.Errorf("transcode: %d files failed", failed) + } + + return nil + }) +} + +// videoTranscodePlan holds a resolved transcode operation for a single video file. +type videoTranscodePlan struct { + IndexPath string + SrcPath string + DestPath string + SizeBytes int64 +} + +// videoBuildTranscodePlans prepares transcode operations and preflight size checks from search results. +func videoBuildTranscodePlans(conf *config.Config, results []search.Photo, force bool) ([]videoTranscodePlan, []videoOutputPlan, error) { + plans := make([]videoTranscodePlan, 0, len(results)) + preflight := make([]videoOutputPlan, 0, len(results)) + + for _, found := range results { + videoFile, ok := videoPrimaryFile(found) + if !ok { + log.Warnf("transcode: missing video file for %s", clean.Log(found.PhotoUID)) + continue + } + + if videoFile.FileSidecar { + log.Warnf("transcode: skipping sidecar file %s", clean.Log(videoFile.FileName)) + continue + } + + if videoFile.MediaType == entity.MediaLive { + log.Warnf("transcode: skipping live photo video %s", clean.Log(videoFile.FileName)) + continue + } + + srcPath := photoprism.FileName(videoFile.FileRoot, videoFile.FileName) + if !fs.FileExistsNotEmpty(srcPath) { + log.Warnf("transcode: missing file %s", clean.Log(srcPath)) + continue + } + + if !conf.SidecarWritable() || !fs.PathWritable(conf.SidecarPath()) { + return nil, nil, config.ErrReadOnly + } + + destPath, err := videoTranscodeTarget(conf, srcPath) + if err != nil { + log.Warnf("transcode: %s", clean.Error(err)) + continue + } + + if destPath == srcPath { + log.Warnf("transcode: skipping because output equals source %s", clean.Log(srcPath)) + continue + } + + if fs.FileExistsNotEmpty(destPath) && !force { + log.Warnf("transcode: output already exists %s", clean.Log(destPath)) + continue + } + + plans = append(plans, videoTranscodePlan{ + IndexPath: srcPath, + SrcPath: srcPath, + DestPath: destPath, + SizeBytes: videoFile.FileSize, + }) + + preflight = append(preflight, videoOutputPlan{ + Destination: destPath, + SizeBytes: videoFile.FileSize, + }) + } + + return plans, preflight, nil +} + +// videoTranscodeTarget computes the sidecar output path for an AVC transcode. +func videoTranscodeTarget(conf *config.Config, srcPath string) (string, error) { + mediaFile, err := photoprism.NewMediaFile(srcPath) + if err != nil { + return "", err + } + + base := videoSidecarPath(srcPath, conf.OriginalsPath(), conf.SidecarPath()) + if mediaFile.IsAnimatedImage() { + return fs.StripKnownExt(base) + fs.ExtMp4, nil + } + + return fs.StripKnownExt(base) + fs.ExtAvc, nil +} + +// videoTranscodeFile runs the transcode operation and returns the resulting media file. +func videoTranscodeFile(conf *config.Config, convert *photoprism.Convert, plan videoTranscodePlan, force bool) (*photoprism.MediaFile, error) { + if convert == nil { + return nil, fmt.Errorf("transcode: convert service unavailable") + } + + mediaFile, err := photoprism.NewMediaFile(plan.SrcPath) + if err != nil { + return nil, err + } + + return convert.ToAvc(mediaFile, conf.FFmpegEncoder(), false, force) +} diff --git a/internal/commands/video_trim.go b/internal/commands/video_trim.go new file mode 100644 index 000000000..191668eda --- /dev/null +++ b/internal/commands/video_trim.go @@ -0,0 +1,346 @@ +package commands + +import ( + "bytes" + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + "time" + + "github.com/manifoldco/promptui" + "github.com/urfave/cli/v2" + + "github.com/photoprism/photoprism/internal/config" + "github.com/photoprism/photoprism/internal/entity" + "github.com/photoprism/photoprism/internal/entity/search" + "github.com/photoprism/photoprism/internal/photoprism" + "github.com/photoprism/photoprism/internal/photoprism/get" + "github.com/photoprism/photoprism/pkg/clean" + "github.com/photoprism/photoprism/pkg/fs" +) + +// VideoTrimCommand configures the command name, flags, and action. +var VideoTrimCommand = &cli.Command{ + Name: "trim", + Usage: "Trims a duration from the start (positive) or end (negative) of matching videos", + ArgsUsage: "[filter]... ", + Flags: []cli.Flag{ + videoCountFlag, + OffsetFlag, + DryRunFlag("prints planned trim operations without writing files"), + YesFlag(), + }, + Action: videoTrimAction, +} + +// videoTrimAction trims matching video files in-place or to sidecar outputs when originals are read-only. +func videoTrimAction(ctx *cli.Context) error { + return CallWithDependencies(ctx, func(conf *config.Config) error { + if conf.DisableFFmpeg() { + return fmt.Errorf("ffmpeg is disabled") + } + + filterArgs, durationArg, err := videoSplitTrimArgs(ctx.Args().Slice()) + if err != nil { + return cli.Exit(err.Error(), 2) + } + + trimDuration, err := videoParseTrimDuration(durationArg) + if err != nil { + return cli.Exit(err.Error(), 2) + } + + filter := videoNormalizeFilter(filterArgs) + results, err := videoSearchResults(filter, ctx.Int(videoCountFlag.Name), ctx.Int(OffsetFlag.Name)) + if err != nil { + return err + } + + plans, preflight, err := videoBuildTrimPlans(conf, results, trimDuration) + if err != nil { + return err + } + + if len(plans) == 0 { + log.Infof("trim: found no matching videos") + return nil + } + + if !ctx.Bool("dry-run") { + if err = videoCheckFreeSpace(preflight); err != nil { + return err + } + } + + if !ctx.Bool("dry-run") && !RunNonInteractively(ctx.Bool("yes")) { + prompt := promptui.Prompt{ + Label: fmt.Sprintf("Trim %d video files?", len(plans)), + IsConfirm: true, + } + if _, err = prompt.Run(); err != nil { + log.Info("trim: cancelled") + return nil + } + } + + var processed, skipped, failed int + convert := get.Convert() + + for _, plan := range plans { + if ctx.Bool("dry-run") { + log.Infof("trim: would trim %s by %s", clean.Log(plan.IndexPath), trimDuration.String()) + skipped++ + continue + } + + if err = videoTrimFile(conf, convert, plan, trimDuration, true); err != nil { + log.Errorf("trim: %s", clean.Error(err)) + failed++ + continue + } + + processed++ + } + + log.Infof("trim: processed %d, skipped %d, failed %d", processed, skipped, failed) + + if failed > 0 { + return fmt.Errorf("trim: %d files failed", failed) + } + + return nil + }) +} + +// videoTrimPlan holds a resolved trim operation for a single video file. +type videoTrimPlan struct { + IndexPath string + SrcPath string + DestPath string + Duration time.Duration + SizeBytes int64 + Sidecar bool +} + +// videoBuildTrimPlans prepares trim operations and preflight size checks from search results. +func videoBuildTrimPlans(conf *config.Config, results []search.Photo, trimDuration time.Duration) ([]videoTrimPlan, []videoOutputPlan, error) { + plans := make([]videoTrimPlan, 0, len(results)) + preflight := make([]videoOutputPlan, 0, len(results)) + + absTrim := trimDuration + if absTrim < 0 { + absTrim = -absTrim + } + + for _, found := range results { + videoFile, ok := videoPrimaryFile(found) + if !ok { + log.Warnf("trim: missing video file for %s", clean.Log(found.PhotoUID)) + continue + } + + if videoFile.FileSidecar { + log.Warnf("trim: skipping sidecar file %s", clean.Log(videoFile.FileName)) + continue + } + + if videoFile.MediaType == entity.MediaLive { + log.Warnf("trim: skipping live photo video %s", clean.Log(videoFile.FileName)) + continue + } + + if videoFile.FileDuration <= 0 { + log.Warnf("trim: missing duration for %s", clean.Log(videoFile.FileName)) + continue + } + + remaining := videoFile.FileDuration - absTrim + if remaining < time.Second { + log.Errorf("trim: duration exceeds available length for %s", clean.Log(videoFile.FileName)) + continue + } + + srcPath := photoprism.FileName(videoFile.FileRoot, videoFile.FileName) + if !fs.FileExistsNotEmpty(srcPath) { + log.Warnf("trim: missing file %s", clean.Log(srcPath)) + continue + } + + destPath := srcPath + useSidecar := false + + if conf.ReadOnly() || !fs.PathWritable(filepath.Dir(srcPath)) || !fs.Writable(srcPath) { + if !conf.SidecarWritable() || !fs.PathWritable(conf.SidecarPath()) { + return nil, nil, config.ErrReadOnly + } + + destPath = videoSidecarPath(srcPath, conf.OriginalsPath(), conf.SidecarPath()) + useSidecar = true + } + + if useSidecar && fs.FileExistsNotEmpty(destPath) { + log.Warnf("trim: output already exists %s", clean.Log(destPath)) + continue + } + + plans = append(plans, videoTrimPlan{ + IndexPath: srcPath, + SrcPath: srcPath, + DestPath: destPath, + Duration: videoFile.FileDuration, + SizeBytes: videoFile.FileSize, + Sidecar: useSidecar, + }) + + preflight = append(preflight, videoOutputPlan{ + Destination: destPath, + SizeBytes: videoFile.FileSize, + }) + } + + return plans, preflight, nil +} + +// videoTrimFile executes the trim operation and refreshes previews/thumbnails before reindexing. +func videoTrimFile(conf *config.Config, convert *photoprism.Convert, plan videoTrimPlan, trimDuration time.Duration, noBackup bool) error { + start := time.Duration(0) + absTrim := trimDuration + if absTrim < 0 { + absTrim = -absTrim + } + + if trimDuration > 0 { + start = absTrim + } + + remaining := plan.Duration - absTrim + if remaining < time.Second { + return fmt.Errorf("remaining duration too short for %s", clean.Log(plan.SrcPath)) + } + + destDir := filepath.Dir(plan.DestPath) + ext := filepath.Ext(plan.DestPath) + if ext == "" { + ext = filepath.Ext(plan.SrcPath) + } + if ext == "" { + ext = ".tmp" + } + + tempPath, err := videoTempPath(destDir, ".trim-*"+ext) + if err != nil { + return err + } + + cmd := videoTrimCmd(conf.FFmpegBin(), plan.SrcPath, tempPath, start, remaining) + cmd.Env = append(cmd.Env, fmt.Sprintf("HOME=%s", conf.CmdCachePath())) + + log.Debugf("ffmpeg: %s", clean.Log(cmd.String())) + + var stderr bytes.Buffer + cmd.Stderr = &stderr + + if err = cmd.Run(); err != nil { + return fmt.Errorf("ffmpeg failed for %s: %s", clean.Log(plan.SrcPath), strings.TrimSpace(stderr.String())) + } + + if !fs.FileExistsNotEmpty(tempPath) { + _ = os.Remove(tempPath) + return fmt.Errorf("trim output missing for %s", clean.Log(plan.SrcPath)) + } + + if err = os.Chmod(tempPath, fs.ModeFile); err != nil { + return err + } + + if plan.Sidecar { + if fs.FileExists(plan.DestPath) { + _ = os.Remove(tempPath) + return fmt.Errorf("output already exists %s", clean.Log(plan.DestPath)) + } + + if err = os.Rename(tempPath, plan.DestPath); err != nil { + _ = os.Remove(tempPath) + return err + } + } else { + if noBackup { + _ = os.Remove(plan.DestPath) + } else { + backupPath := plan.DestPath + ".backup" + if fs.FileExists(backupPath) { + _ = os.Remove(backupPath) + } + if err = os.Rename(plan.DestPath, backupPath); err != nil { + _ = os.Remove(tempPath) + return err + } + _ = os.Chmod(backupPath, fs.ModeBackupFile) + } + + if err = os.Rename(tempPath, plan.DestPath); err != nil { + _ = os.Remove(tempPath) + return err + } + } + + mediaFile, err := photoprism.NewMediaFile(plan.DestPath) + if err != nil { + return err + } + + if convert != nil { + if img, imgErr := convert.ToImage(mediaFile, true); imgErr != nil { + log.Warnf("trim: %s", clean.Error(imgErr)) + } else if img != nil { + if thumbsErr := img.GenerateThumbnails(conf.ThumbCachePath(), true); thumbsErr != nil { + log.Warnf("trim: %s", clean.Error(thumbsErr)) + } + } + } + + return videoReindexRelated(conf, plan.IndexPath) +} + +// videoTrimCmd builds an ffmpeg command that trims a source file with stream copy. +func videoTrimCmd(ffmpegBin, srcName, destName string, start, duration time.Duration) *exec.Cmd { + args := []string{ + "-hide_banner", + "-y", + } + + if start > 0 { + args = append(args, "-ss", videoFFmpegSeconds(start)) + } + + args = append(args, + "-i", srcName, + "-t", videoFFmpegSeconds(duration), + "-map", "0", + "-dn", + "-ignore_unknown", + "-codec", "copy", + "-avoid_negative_ts", "make_zero", + ) + + if videoTrimFastStart(destName) { + args = append(args, "-movflags", "+faststart") + } + + args = append(args, destName) + + // #nosec G204 -- arguments are built from validated inputs and config. + return exec.Command(ffmpegBin, args...) +} + +// videoTrimFastStart reports whether the trim output should enable faststart for MP4/MOV containers. +func videoTrimFastStart(destName string) bool { + switch strings.ToLower(filepath.Ext(destName)) { + case fs.ExtMp4, fs.ExtMov, fs.ExtQT, ".m4v": + return true + default: + return false + } +} diff --git a/internal/commands/video_trim_test.go b/internal/commands/video_trim_test.go new file mode 100644 index 000000000..df0177962 --- /dev/null +++ b/internal/commands/video_trim_test.go @@ -0,0 +1,17 @@ +package commands + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +// TestVideoTrimFastStart verifies which extensions get the faststart flag. +func TestVideoTrimFastStart(t *testing.T) { + assert.True(t, videoTrimFastStart("clip.mp4")) + assert.True(t, videoTrimFastStart("clip.MOV")) + assert.True(t, videoTrimFastStart("clip.m4v")) + assert.True(t, videoTrimFastStart("clip.qt")) + assert.False(t, videoTrimFastStart("clip.mkv")) + assert.False(t, videoTrimFastStart("")) +} diff --git a/internal/commands/videos.go b/internal/commands/videos.go new file mode 100644 index 000000000..56596610f --- /dev/null +++ b/internal/commands/videos.go @@ -0,0 +1,38 @@ +package commands + +import "github.com/urfave/cli/v2" + +// VideosCommands configures the CLI subcommands for working with indexed videos. +var VideosCommands = &cli.Command{ + Name: "videos", + Aliases: []string{"video"}, + Usage: "Video troubleshooting and editing subcommands", + Subcommands: []*cli.Command{ + VideoListCommand, + VideoTrimCommand, + VideoRemuxCommand, + VideoTranscodeCommand, + VideoInfoCommand, + }, +} + +// videoCountFlag limits the number of results returned by video commands. +var videoCountFlag = &cli.IntFlag{ + Name: "count", + Aliases: []string{"n"}, + Usage: "maximum `NUMBER` of results", + Value: 10000, +} + +// videoForceFlag allows overwriting existing output files for remux/transcode. +var videoForceFlag = &cli.BoolFlag{ + Name: "force", + Aliases: []string{"f"}, + Usage: "replace existing output files", +} + +// videoVerboseFlag adds raw metadata to video info output. +var videoVerboseFlag = &cli.BoolFlag{ + Name: "verbose", + Usage: "include raw metadata output", +} diff --git a/internal/form/search_photos.go b/internal/form/search_photos.go index 4eb1a69b0..786bd1518 100644 --- a/internal/form/search_photos.go +++ b/internal/form/search_photos.go @@ -46,7 +46,7 @@ type SearchPhotos struct { Square bool `form:"square" notes:"Finds square pictures only (aspect ratio 1:1)"` Archived bool `form:"archived" notes:"Finds archived content"` Public bool `form:"public" notes:"Excludes private content"` - Private bool `form:"private" notes:"Finds private content"` + Private bool `form:"private" notes:"Finds private content only (except when public:true)"` Review bool `form:"review" notes:"Finds content in review"` Error bool `form:"error" notes:"Finds content with errors"` Hidden bool `form:"hidden" notes:"Finds hidden content (broken or unsupported)"` diff --git a/internal/form/search_photos_geo.go b/internal/form/search_photos_geo.go index d1e1d5a7a..bbe3a4624 100644 --- a/internal/form/search_photos_geo.go +++ b/internal/form/search_photos_geo.go @@ -47,7 +47,7 @@ type SearchPhotosGeo struct { Square bool `form:"square" notes:"Finds square pictures only (aspect ratio 1:1)"` Archived bool `form:"archived" notes:"Finds archived content"` Public bool `form:"public" notes:"Excludes private content"` - Private bool `form:"private" notes:"Finds private content"` + Private bool `form:"private" notes:"Finds private content only (except when public:true)"` Review bool `form:"review" notes:"Finds content in review"` Quality int `form:"quality" notes:"Minimum quality score (1-7)"` Face string `form:"face" notes:"Find pictures with a specific face ID, you can also specify yes, no, new, or a face type"` diff --git a/internal/photoprism/convert_video_avc.go b/internal/photoprism/convert_video_avc.go index dd5bc54b3..40aa159d9 100644 --- a/internal/photoprism/convert_video_avc.go +++ b/internal/photoprism/convert_video_avc.go @@ -46,7 +46,7 @@ func (w *Convert) ToAvc(f *MediaFile, encoder encode.Encoder, noMutex, force boo if mp4Name, mp4Err := fs.FileName(f.FileName(), w.conf.SidecarPath(), w.conf.OriginalsPath(), fs.ExtMp4); mp4Err != nil { return nil, fmt.Errorf("convert: %s in %s (remux)", mp4Err, clean.Log(f.RootRelName())) } else if mp4Err = ffmpeg.RemuxFile(f.FileName(), mp4Name, encode.NewRemuxOptions(conf.FFmpegBin(), fs.VideoMp4, false)); mp4Err != nil { - return nil, fmt.Errorf("convert: %s in %s (remux)", err, clean.Log(f.RootRelName())) + return nil, fmt.Errorf("convert: %s in %s (remux)", mp4Err, clean.Log(f.RootRelName())) } else if mp4File, fileErr := NewMediaFile(mp4Name); mp4File == nil || fileErr != nil { log.Warnf("convert: %s could not be converted to mp4", logFileName) } else if jsonErr := mp4File.CreateExifToolJson(w); jsonErr != nil { diff --git a/internal/photoprism/mediafile_vision.go b/internal/photoprism/mediafile_vision.go index 1ca330f14..4f487cdb4 100644 --- a/internal/photoprism/mediafile_vision.go +++ b/internal/photoprism/mediafile_vision.go @@ -38,7 +38,7 @@ func (m *MediaFile) GenerateCaption(captionSrc entity.Src) (caption *vision.Capt fileName, fileErr := m.Thumbnail(Config().ThumbCachePath(), size.Name) if fileErr != nil { - return caption, err + return caption, fileErr } // Get matching labels from computer vision model. @@ -101,7 +101,7 @@ func (m *MediaFile) GenerateLabels(labelSrc entity.Src) (labels classify.Labels) // Get thumbnail filenames for the selected sizes. for _, s := range sizes { if thumbnail, fileErr := m.Thumbnail(Config().ThumbCachePath(), s); fileErr != nil { - log.Debugf("index: %s in %s", err, clean.Log(m.RootRelName())) + log.Debugf("index: %s in %s", fileErr, clean.Log(m.RootRelName())) continue } else { thumbnails = append(thumbnails, thumbnail) diff --git a/internal/thumb/vips.go b/internal/thumb/vips.go index fdf712911..a45a62269 100644 --- a/internal/thumb/vips.go +++ b/internal/thumb/vips.go @@ -79,6 +79,7 @@ func Vips(imageName string, imageBuffer []byte, hash, thumbPath string, width, h // Use defaults. } + // Embed an ICC profile when a JPEG declares its color space via the EXIF InteroperabilityIndex tag. if err = vipsSetIccProfileForInteropIndex(img, clean.Log(filepath.Base(imageName))); err != nil { log.Debugf("vips: %s in %s (set icc profile for interop index tag)", err, clean.Log(filepath.Base(imageName))) } diff --git a/internal/thumb/vips_icc.go b/internal/thumb/vips_icc.go index bc63246a2..77052ec59 100644 --- a/internal/thumb/vips_icc.go +++ b/internal/thumb/vips_icc.go @@ -27,10 +27,28 @@ const ( // but lacks an embedded profile. If an ICC profile is already present, it // leaves the image untouched. func vipsSetIccProfileForInteropIndex(img *vips.ImageRef, logName string) (err error) { + if img.HasICCProfile() { + return nil + } + // Some cameras signal color space via EXIF InteroperabilityIndex instead of // embedding an ICC profile. Browsers and libvips ignore this tag, so we // inject a matching ICC profile to produce correct thumbnails. - iiFull := img.GetString("exif-ifd4-InteroperabilityIndex") + iiField := "exif-ifd4-InteroperabilityIndex" + hasInterop := false + + for _, field := range img.GetFields() { + if field == iiField { + hasInterop = true + break + } + } + + if !hasInterop { + return nil + } + + iiFull := img.GetString(iiField) if iiFull == "" { return nil @@ -40,19 +58,14 @@ func vipsSetIccProfileForInteropIndex(img *vips.ImageRef, logName string) (err e // a string with a trailing space. Using the first three bytes covers the // meaningful code (e.g., "R03", "R98"). if len(iiFull) < 3 { - log.Debugf("interopindex: %s has unexpected interop index %q", logName, iiFull) + log.Debugf("vips: %s has unexpected interop index %q", logName, iiFull) return nil } ii := iiFull[:3] - log.Tracef("interopindex: %s read exif and got interopindex %s, %s", logName, ii, iiFull) + log.Tracef("vips: %s read exif and got interopindex %s, %s", logName, ii, iiFull) - if img.HasICCProfile() { - log.Debugf("interopindex: %s already has an embedded ICC profile; skipping fallback.", logName) - return nil - } - - profilePath := "" + var profilePath string switch ii { case InteropIndexAdobeRGB: @@ -60,14 +73,14 @@ func vipsSetIccProfileForInteropIndex(img *vips.ImageRef, logName string) (err e profilePath, err = GetIccProfile(IccAdobeRGBCompat, IccAdobeRGBCompatV2, IccAdobeRGBCompatV4) if err != nil { - return fmt.Errorf("interopindex %s: %w", ii, err) + return fmt.Errorf("vips: failed to get %s profile for %s (%w)", ii, logName, err) } case InteropIndexSRGB: // sRGB: browsers and libvips assume sRGB by default, so no embed needed. case InteropIndexThumb: // Thumbnail file; specification unclear—treat as sRGB and do nothing. default: - log.Debugf("interopindex: %s has unknown interop index %s", logName, ii) + log.Debugf("vips: %s has unknown interop index %s", logName, ii) } if profilePath == "" { diff --git a/scripts/dist/Makefile b/scripts/dist/Makefile index b89ae02fe..3a50ce118 100755 --- a/scripts/dist/Makefile +++ b/scripts/dist/Makefile @@ -65,8 +65,8 @@ install-clitools: @apt-get -qq install zsh nano >/dev/null 2>&1 && echo "init: successfully installed zsh and nano" || echo "init: packages zsh and nano not available for installation" @apt-get -qq install exa duf >/dev/null 2>&1 && echo "init: successfully installed exa and duf" || echo "init: packages exa and duf not available for installation" darktable: - echo 'deb http://download.opensuse.org/repositories/graphics:/darktable/xUbuntu_23.04/ /' | sudo tee /etc/apt/sources.list.d/graphics:darktable.list - curl -fsSL https://download.opensuse.org/repositories/graphics:darktable/xUbuntu_23.04/Release.key | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/graphics_darktable.gpg > /dev/null + echo 'deb http://download.opensuse.org/repositories/graphics:/darktable/xUbuntu_25.10/ /' | sudo tee /etc/apt/sources.list.d/graphics:darktable.list + curl -fsSL https://download.opensuse.org/repositories/graphics:darktable/xUbuntu_25.10/Release.key | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/graphics_darktable.gpg > /dev/null sudo apt-get update sudo apt-get install darktable sudo apt-get autoremove diff --git a/scripts/dist/install-onnx.sh b/scripts/dist/install-onnx.sh index 222f520ae..c39e7a94b 100755 --- a/scripts/dist/install-onnx.sh +++ b/scripts/dist/install-onnx.sh @@ -2,7 +2,7 @@ set -euo pipefail -ONNX_VERSION=${ONNX_VERSION:-1.22.0} +ONNX_VERSION=${ONNX_VERSION:-1.23.2} TODAY=$(date -u +%Y%m%d) TMPDIR=${TMPDIR:-/tmp} SYSTEM=$(uname -s) @@ -30,11 +30,11 @@ case "${SYSTEM}" in case "${ARCH}" in amd64|AMD64|x86_64|x86-64) archive="onnxruntime-linux-x64-${ONNX_VERSION}.tgz" - sha="8344d55f93d5bc5021ce342db50f62079daf39aaafb5d311a451846228be49b3" + sha="1fa4dcaef22f6f7d5cd81b28c2800414350c10116f5fdd46a2160082551c5f9b" ;; arm64|ARM64|aarch64) archive="onnxruntime-linux-aarch64-${ONNX_VERSION}.tgz" - sha="bb76395092d150b52c7092dc6b8f2fe4d80f0f3bf0416d2f269193e347e24702" + sha="7c63c73560ed76b1fac6cff8204ffe34fe180e70d6582b5332ec094810241e5c" ;; *) echo "Warning: ONNX Runtime is not provided for Linux/${ARCH}; skipping install." >&2 @@ -46,7 +46,7 @@ case "${SYSTEM}" in case "${ARCH}" in arm64|ARM64|aarch64|x86_64|x86-64) archive="onnxruntime-osx-universal2-${ONNX_VERSION}.tgz" - sha="cfa6f6584d87555ed9f6e7e8a000d3947554d589efe3723b8bfa358cd263d03c" + sha="49ae8e3a66ccb18d98ad3fe7f5906b6d7887df8a5edd40f49eb2b14e20885809" ;; *) echo "Unsupported macOS architecture '${ARCH}'." >&2 diff --git a/setup/pkg/linux/README.html b/setup/pkg/linux/README.html index def160e69..5392f954d 100644 --- a/setup/pkg/linux/README.html +++ b/setup/pkg/linux/README.html @@ -698,6 +698,6 @@ header, .context-menu, .megamenu-content, footer{ README
-

PhotoPrism® Installation Packages

As an alternative to our Docker images, you can use the packages available at dl.photoprism.app/pkg/linux/ to install PhotoPrism on compatible Linux distributions without building it from source.

These binary installation packages are intended for experienced users and maintainers of third-party integrations only, as they require manual configuration and do not include tested system dependencies. Since we are unable to provide support for custom installations, we recommend using one of our Docker images to run PhotoPrism on a private server or NAS device.

Also note that the minimum required glibc version is 2.35, so for example Ubuntu 22.04 and Debian Bookworm will work, but older Linux distributions may not be compatible.

Usage

Installation Using tar.gz Archives

You can download and install PhotoPrism in /opt/photoprism by running the following commands:

If your server has an ARM-based CPU, please make sure to install arm64.tar.gz instead of amd64.tar.gz when using the commands above. Both are linked to the latest stable release.

Since the packages currently do not include a default configuration, we recommend that you create a defaults.yml in /etc/photoprism next, in which you configure the paths and other settings that you want to use for your instance.

.deb Packages for Ubuntu / Debian Linux

As an alternative to the plain tar.gz archives, that you need to unpack manually, we also offer .deb packages for Debian-based distributions such as Ubuntu Linux. They install PhotoPrism under /opt/photoprism, add a /usr/local/bin/photoprism symlink, create /etc/photoprism/defaults.yml, and pull in the runtime libraries listed in the Dependencies section.

On servers with a 64-bit Intel or AMD CPU, our latest stable release can be installed as follows:

If your server has an ARM-based CPU, such as a Raspberry Pi, use the following commands instead:

Omit --no-install-recommends if you want APT to install MariaDB, Darktable, RawTherapee, ImageMagick, and other recommended extras automatically.

.rpm Packages for Fedora / RHEL / openSUSE

For RPM-based distributions we publish .rpm packages that mirror the layout described above. Install the latest release on x86_64 hardware with:

and on aarch64 (ARM64):

Replace dnf with zypper on openSUSE (use --allow-unsigned-rpm when required). On distributions that do not ship FFmpeg in the base repositories, enable the appropriate multimedia repository (EPEL, RPM Fusion, Packman, …) before installing the dependencies below.

AUR Packages for Arch Linux

Thomas Eizinger additionally maintains AUR packages for installation on Arch Linux. They are based on our tar.gz packages and have a systemd integration so that PhotoPrism can be started and restarted automatically.

Learn more ›

Updates

To update your installation, please stop all running PhotoPrism instances and make sure that there are no media, database, or custom config files in the /opt/photoprism directory. You can then delete its contents with the command sudo rm -rf /opt/photoprism/* and install a new version as shown above.

If you have used a .deb package for installation, you may need to remove the currently installed photoprism package by running sudo dpkg -r photoprism before you can install a new version with sudo apt install ./package.deb or sudo dpkg -i package.deb.

Dependencies

PhotoPrism packages bundle TensorFlow 2.18.0 and, starting with the October 2025 builds, ONNX Runtime 1.22.0 as described in ai/face/README.md. The shared libraries for both frameworks are shipped inside /opt/photoprism/lib, so no additional system packages are needed to switch PHOTOPRISM_FACE_ENGINE to onnx. The binaries still rely on glibc ≥ 2.35 and the standard C/C++ runtime libraries (libstdc++6, libgcc_s1, libgomp1, …) provided by your distribution.

Required Runtime Packages

Install the following packages before running PhotoPrism so that thumbnailing, metadata extraction, and the SQLite fallback database work out of the box:

Distribution familyCommand
Debian / Ubuntusudo apt install libvips42t64 libimage-exiftool-perl ffmpeg sqlite3 tzdata
Use libvips42 or, as a fallback, libvips-dev on older releases.
Fedora / RHEL / Alma / Rockysudo dnf install vips perl-Image-ExifTool ffmpeg sqlite tzdata
openSUSEsudo zypper install vips perl-Image-ExifTool ffmpeg sqlite3 tzdata

These packages pull in the full libvips stack (GLib, libjpeg/libtiff/libwebp, archive/zstd, etc.) that the PhotoPrism binary links against. Run ldd /opt/photoprism/bin/photoprism if you need to diagnose missing libraries on custom distributions.

For extended RAW processing, HEIF/HEIC support, and database scalability we recommend installing:

  • MariaDB or MariaDB Server (external database)

  • Darktable and/or RawTherapee (RAW converters)

  • ImageMagick (CLI utilities)

  • libheif (prefer the up-to-date binaries from dl.photoprism.app/dist/libheif/; install with bash <(curl -s https://raw.githubusercontent.com/photoprism/photoprism/develop/scripts/dist/install-libheif.sh) when distro packages are outdated)

  • librsvg2-bin or librsvg2-tools (SVG conversion helpers)

Use sudo apt install, sudo dnf install, or sudo zypper install with the package names above to pull them in as needed.

We publish the same libheif builds that ship in our Docker images. They include fixes for rotation metadata and newer iOS HEIC variants that are often missing from distribution packages. Advanced users can regenerate them via make build-libheif-*, which calls scripts/dist/build-libheif.sh for each supported base image and architecture before uploading the archives to dl.photoprism.app.

Keep in mind that even if all dependencies are installed, it is possible that you are using a version that is not fully compatible with your pictures, phone, or camera. Our team cannot provide support in these cases if the same issue does not occur with our official Docker images. Details on the packages and package versions we use can be found in the Dockerfiles available in our public project repository.

Configuration

After unpacking the binaries you only need a writable configuration and storage location. The typical workflow is:

  1. Inspect the current settings: photoprism config

  2. Create dedicated directories for runtime data, for example (replace photoprism:photoprism with the user/group that should own the data):

  3. Update /etc/photoprism/defaults.yml (or .yaml) so ConfigPath, StoragePath, OriginalsPath, and ImportPath point outside the installation directory. When the /etc file is missing or empty, PhotoPrism automatically loads <config-path>/defaults.yml, so you may edit the copy under PHOTOPRISM_CONFIG_PATH instead.

  4. Optionally place per-instance overrides in <config-path>/options.yml.

  5. Restart the PhotoPrism service (or rerun the CLI command) so the changes take effect.

Run photoprism --help in a terminal to get an overview of the command flags and environment variables available for configuration. Their current values can always be displayed with the photoprism config command.

Precedence

PhotoPrism reads settings in the following order (later entries override earlier ones):

OrderSourceNotes
1Built-in defaultsHard-coded, fall back when nothing else is set.
2/etc/photoprism/defaults.ymlGlobal defaults for all users on the host; falls back to <config-path>/defaults.yml (respects .yml / .yaml) when missing or empty.
3Environment variables / CLI flagsCombine with service managers or wrappers.
4<config-path>/options.ymlInstance-specific overrides (per user or per deployment).
5Runtime changes in the UIPersisted to options.yml; require a restart when running outside Docker.

If no explicit originals, import and/or assets path has been configured, a list of default directory paths will be searched and the first existing directory will be used for the respective path. To simplify updates, we recommend not storing media, database files, or custom configs in the installation directory itself (for example /opt/photoprism); use another base such as /var/lib/photoprism or a path under the user’s home directory.

All configuration changes—whether made via UI, config files, or environment variablesrequire a restart to take effect when PhotoPrism runs as a standalone process.

defaults.yml

Packages install a starter /etc/photoprism/defaults.yml. Adjust it with root privileges to set global defaults such as filesystem locations, database options, and network ports. If you delete or leave that file empty, PhotoPrism automatically falls back to <config-path>/defaults.yml, so copying the sample there keeps the same behavior without touching /etc. When specifying strings you can use ~ as the current user’s home directory and relative paths starting with ./:

For a list of supported options and their names, see https://docs.photoprism.app/getting-started/config-files/#config-options.

When specifying values, make sure that the data type is the same as in the documentation, e.g. bool values must be either true or false and int values must be whole numbers without any quotes like in the example above.

options.yml

Default config values can be overridden by values specified in an options.yml file as well as with command flags and environment variables. To load values from an existing options.yml file, you can specify its storage path (excluding the filename) by setting the ConfigPath option in your defaults.yml file, using the --config-path command flag, or with the PHOTOPRISM_CONFIG_PATH environment variable.

The values in an options.yml file are not global and can be used to customize individual instances e.g. based on the default values in a defaults.yml file. Both files allow you to set any of the supported options.

Tip: when running PhotoPrism as a systemd service, export environment variables in the service unit or in /etc/default/photoprism. For interactive shells, specify the corresponding flags or prefix commands with variables (for example PHOTOPRISM_DEBUG=true photoprism index). Use the smallest scope that fits your deployment so updates stay manageable.

Documentation

For detailed information on specific features and related resources, see our Knowledge Base, or check the User Guide for help navigating the user interface, a complete list of config options, and other installation methods:

Getting Support

If you need help installing our software at home, you are welcome to post your question in GitHub Discussions or ask in our Community Chat. Common problems can be quickly diagnosed and solved using our Troubleshooting Checklists. Silver, Gold, and Platinum members are also welcome to email us for technical support and advice.

View Support Options ›

+

PhotoPrism® Installation Packages

As an alternative to our Docker images, you can use the packages available at dl.photoprism.app/pkg/linux/ to install PhotoPrism on compatible Linux distributions without building it from source.

These binary installation packages are intended for experienced users and maintainers of third-party integrations only, as they require manual configuration and do not include tested system dependencies. Since we are unable to provide support for custom installations, we recommend using one of our Docker images to run PhotoPrism on a private server or NAS device.

Also note that the minimum required glibc version is 2.35, so for example Ubuntu 22.04 and Debian Bookworm will work, but older Linux distributions may not be compatible.

Usage

Installation Using tar.gz Archives

You can download and install PhotoPrism in /opt/photoprism by running the following commands:

If your server has an ARM-based CPU, please make sure to install arm64.tar.gz instead of amd64.tar.gz when using the commands above. Both are linked to the latest stable release.

Since the packages currently do not include a default configuration, we recommend that you create a defaults.yml in /etc/photoprism next, in which you configure the paths and other settings that you want to use for your instance.

.deb Packages for Ubuntu / Debian Linux

As an alternative to the plain tar.gz archives, that you need to unpack manually, we also offer .deb packages for Debian-based distributions such as Ubuntu Linux. They install PhotoPrism under /opt/photoprism, add a /usr/local/bin/photoprism symlink, create /etc/photoprism/defaults.yml, and pull in the runtime libraries listed in the Dependencies section.

On servers with a 64-bit Intel or AMD CPU, our latest stable release can be installed as follows:

If your server has an ARM-based CPU, such as a Raspberry Pi, use the following commands instead:

Omit --no-install-recommends if you want APT to install MariaDB, Darktable, RawTherapee, ImageMagick, and other recommended extras automatically.

.rpm Packages for Fedora / RHEL / openSUSE

For RPM-based distributions we publish .rpm packages that mirror the layout described above. Install the latest release on x86_64 hardware with:

and on aarch64 (ARM64):

Replace dnf with zypper on openSUSE (use --allow-unsigned-rpm when required). On distributions that do not ship FFmpeg in the base repositories, enable the appropriate multimedia repository (EPEL, RPM Fusion, Packman, …) before installing the dependencies below.

AUR Packages for Arch Linux

Thomas Eizinger additionally maintains AUR packages for installation on Arch Linux. They are based on our tar.gz packages and have a systemd integration so that PhotoPrism can be started and restarted automatically.

Learn more ›

Updates

To update your installation, please stop all running PhotoPrism instances and make sure that there are no media, database, or custom config files in the /opt/photoprism directory. You can then delete its contents with the command sudo rm -rf /opt/photoprism/* and install a new version as shown above.

If you have used a .deb package for installation, you may need to remove the currently installed photoprism package by running sudo dpkg -r photoprism before you can install a new version with sudo apt install ./package.deb or sudo dpkg -i package.deb.

Dependencies

PhotoPrism packages bundle TensorFlow 2.18.0 and, starting with the December 2025 builds, ONNX Runtime 1.23.2 as described in ai/face/README.md. The shared libraries for both frameworks are shipped inside /opt/photoprism/lib, so no additional system packages are needed to switch PHOTOPRISM_FACE_ENGINE to onnx. The binaries still rely on glibc ≥ 2.35 and the standard C/C++ runtime libraries (libstdc++6, libgcc_s1, libgomp1, …) provided by your distribution.

Required Runtime Packages

Install the following packages before running PhotoPrism so that thumbnailing, metadata extraction, and the SQLite fallback database work out of the box:

Distribution familyCommand
Debian / Ubuntusudo apt install libvips42t64 libimage-exiftool-perl ffmpeg sqlite3 tzdata
Use libvips42 or, as a fallback, libvips-dev on older releases.
Fedora / RHEL / Alma / Rockysudo dnf install vips perl-Image-ExifTool ffmpeg sqlite tzdata
openSUSEsudo zypper install vips perl-Image-ExifTool ffmpeg sqlite3 tzdata

These packages pull in the full libvips stack (GLib, libjpeg/libtiff/libwebp, archive/zstd, etc.) that the PhotoPrism binary links against. Run ldd /opt/photoprism/bin/photoprism if you need to diagnose missing libraries on custom distributions.

For extended RAW processing, HEIF/HEIC support, and database scalability we recommend installing:

  • MariaDB or MariaDB Server (external database)

  • Darktable and/or RawTherapee (RAW converters)

  • ImageMagick (CLI utilities)

  • libheif (prefer the up-to-date binaries from dl.photoprism.app/dist/libheif/; install with bash <(curl -s https://raw.githubusercontent.com/photoprism/photoprism/develop/scripts/dist/install-libheif.sh) when distro packages are outdated)

  • librsvg2-bin or librsvg2-tools (SVG conversion helpers)

Use sudo apt install, sudo dnf install, or sudo zypper install with the package names above to pull them in as needed.

We publish the same libheif builds that ship in our Docker images. They include fixes for rotation metadata and newer iOS HEIC variants that are often missing from distribution packages. Advanced users can regenerate them via make build-libheif-*, which calls scripts/dist/build-libheif.sh for each supported base image and architecture before uploading the archives to dl.photoprism.app.

Keep in mind that even if all dependencies are installed, it is possible that you are using a version that is not fully compatible with your pictures, phone, or camera. Our team cannot provide support in these cases if the same issue does not occur with our official Docker images. Details on the packages and package versions we use can be found in the Dockerfiles available in our public project repository.

Configuration

After unpacking the binaries you only need a writable configuration and storage location. The typical workflow is:

  1. Inspect the current settings: photoprism config

  2. Create dedicated directories for runtime data, for example (replace photoprism:photoprism with the user/group that should own the data):

  3. Update /etc/photoprism/defaults.yml (or .yaml) so ConfigPath, StoragePath, OriginalsPath, and ImportPath point outside the installation directory. When the /etc file is missing or empty, PhotoPrism automatically loads <config-path>/defaults.yml, so you may edit the copy under PHOTOPRISM_CONFIG_PATH instead.

  4. Optionally place per-instance overrides in <config-path>/options.yml.

  5. Restart the PhotoPrism service (or rerun the CLI command) so the changes take effect.

Run photoprism --help in a terminal to get an overview of the command flags and environment variables available for configuration. Their current values can always be displayed with the photoprism config command.

Precedence

PhotoPrism reads settings in the following order (later entries override earlier ones):

OrderSourceNotes
1Built-in defaultsHard-coded, fall back when nothing else is set.
2/etc/photoprism/defaults.ymlGlobal defaults for all users on the host; falls back to <config-path>/defaults.yml (respects .yml / .yaml) when missing or empty.
3Environment variables / CLI flagsCombine with service managers or wrappers.
4<config-path>/options.ymlInstance-specific overrides (per user or per deployment).
5Runtime changes in the UIPersisted to options.yml; require a restart when running outside Docker.

If no explicit originals, import and/or assets path has been configured, a list of default directory paths will be searched and the first existing directory will be used for the respective path. To simplify updates, we recommend not storing media, database files, or custom configs in the installation directory itself (for example /opt/photoprism); use another base such as /var/lib/photoprism or a path under the user’s home directory.

All configuration changes—whether made via UI, config files, or environment variablesrequire a restart to take effect when PhotoPrism runs as a standalone process.

defaults.yml

Packages install a starter /etc/photoprism/defaults.yml. Adjust it with root privileges to set global defaults such as filesystem locations, database options, and network ports. If you delete or leave that file empty, PhotoPrism automatically falls back to <config-path>/defaults.yml, so copying the sample there keeps the same behavior without touching /etc. When specifying strings you can use ~ as the current user’s home directory and relative paths starting with ./:

For a list of supported options and their names, see https://docs.photoprism.app/getting-started/config-files/#config-options.

When specifying values, make sure that the data type is the same as in the documentation, e.g. bool values must be either true or false and int values must be whole numbers without any quotes like in the example above.

options.yml

Default config values can be overridden by values specified in an options.yml file as well as with command flags and environment variables. To load values from an existing options.yml file, you can specify its storage path (excluding the filename) by setting the ConfigPath option in your defaults.yml file, using the --config-path command flag, or with the PHOTOPRISM_CONFIG_PATH environment variable.

The values in an options.yml file are not global and can be used to customize individual instances e.g. based on the default values in a defaults.yml file. Both files allow you to set any of the supported options.

Tip: when running PhotoPrism as a systemd service, export environment variables in the service unit or in /etc/default/photoprism. For interactive shells, specify the corresponding flags or prefix commands with variables (for example PHOTOPRISM_DEBUG=true photoprism index). Use the smallest scope that fits your deployment so updates stay manageable.

Documentation

For detailed information on specific features and related resources, see our Knowledge Base, or check the User Guide for help navigating the user interface, a complete list of config options, and other installation methods:

Getting Support

If you need help installing our software at home, you are welcome to post your question in GitHub Discussions or ask in our Community Chat. Common problems can be quickly diagnosed and solved using our Troubleshooting Checklists. Silver, Gold, and Platinum members are also welcome to email us for technical support and advice.

View Support Options ›

\ No newline at end of file diff --git a/setup/pkg/linux/README.md b/setup/pkg/linux/README.md index fc3fcb0c2..5fb868277 100644 --- a/setup/pkg/linux/README.md +++ b/setup/pkg/linux/README.md @@ -74,7 +74,7 @@ If you have used a *.deb* package for installation, you may need to remove the c ## Dependencies -PhotoPrism packages bundle TensorFlow 2.18.0 and, starting with the October 2025 builds, ONNX Runtime 1.22.0 as described in [`ai/face/README.md`](https://github.com/photoprism/photoprism/blob/develop/internal/ai/face/README.md). The shared libraries for both frameworks are shipped inside `/opt/photoprism/lib`, so no additional system packages are needed to switch `PHOTOPRISM_FACE_ENGINE` to `onnx`. The binaries still rely on glibc ≥ 2.35 and the standard C/C++ runtime libraries (`libstdc++6`, `libgcc_s1`, `libgomp1`, …) provided by your distribution. +PhotoPrism packages bundle TensorFlow 2.18.0 and, starting with the December 2025 builds, ONNX Runtime 1.23.2 as described in [`ai/face/README.md`](https://github.com/photoprism/photoprism/blob/develop/internal/ai/face/README.md). The shared libraries for both frameworks are shipped inside `/opt/photoprism/lib`, so no additional system packages are needed to switch `PHOTOPRISM_FACE_ENGINE` to `onnx`. The binaries still rely on glibc ≥ 2.35 and the standard C/C++ runtime libraries (`libstdc++6`, `libgcc_s1`, `libgomp1`, …) provided by your distribution. ### Required Runtime Packages